mirror of
https://github.com/hyprwm/hyprlock.git
synced 2024-12-22 05:19:48 +01:00
input-field: fail display improvments (#154)
* input-field: fail display improvments * update Home Manager * add `$ATTEMPTS` variable, change defaults * nix wording * log failed attempts
This commit is contained in:
parent
cb08f60254
commit
149b6737c2
6 changed files with 187 additions and 35 deletions
|
@ -261,6 +261,24 @@ in {
|
|||
default = -1;
|
||||
};
|
||||
|
||||
fail_color = mkOption {
|
||||
description = "If authentication failed, changes outer color and fail message color";
|
||||
type = str;
|
||||
default = "rgb(204, 34, 34)";
|
||||
};
|
||||
|
||||
fail_text = mkOption {
|
||||
description = "The text shown if authentication failed. $FAIL (reason) and $ATTEMPTS variables are available";
|
||||
type = str;
|
||||
default = "<i>$FAIL</i>";
|
||||
};
|
||||
|
||||
fail_transition = mkOption {
|
||||
description = "The transition time (ms) between normal outer color and fail color";
|
||||
type = int;
|
||||
default = 300;
|
||||
};
|
||||
|
||||
position = {
|
||||
x = mkOption {
|
||||
description = "X position of the label";
|
||||
|
@ -414,6 +432,9 @@ in {
|
|||
shadow_size = ${toString input-field.shadow_size}
|
||||
shadow_color = ${input-field.shadow_color}
|
||||
shadow_boost = ${toString input-field.shadow_boost}
|
||||
fail_color = ${input-field.fail_color}
|
||||
fail_text = ${input-field.fail_text}
|
||||
fail_transition = ${toString input-field.fail_transition}
|
||||
|
||||
position = ${toString input-field.position.x}, ${toString input-field.position.y}
|
||||
halign = ${input-field.halign}
|
||||
|
|
|
@ -78,6 +78,9 @@ void CConfigManager::init() {
|
|||
m_config.addSpecialConfigValue("input-field", "placeholder_text", Hyprlang::STRING{"<i>Input Password</i>"});
|
||||
m_config.addSpecialConfigValue("input-field", "hide_input", Hyprlang::INT{0});
|
||||
m_config.addSpecialConfigValue("input-field", "rounding", Hyprlang::INT{-1});
|
||||
m_config.addSpecialConfigValue("input-field", "fail_color", Hyprlang::INT{0xFFCC2222});
|
||||
m_config.addSpecialConfigValue("input-field", "fail_text", Hyprlang::STRING{"<i>$FAIL</i>"});
|
||||
m_config.addSpecialConfigValue("input-field", "fail_transition", Hyprlang::INT{300});
|
||||
SHADOWABLE("input-field");
|
||||
|
||||
m_config.addSpecialCategory("label", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
|
||||
|
@ -165,6 +168,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
{"placeholder_text", m_config.getSpecialConfigValue("input-field", "placeholder_text", k.c_str())},
|
||||
{"hide_input", m_config.getSpecialConfigValue("input-field", "hide_input", k.c_str())},
|
||||
{"rounding", m_config.getSpecialConfigValue("input-field", "rounding", k.c_str())},
|
||||
{"fail_color", m_config.getSpecialConfigValue("input-field", "fail_color", k.c_str())},
|
||||
{"fail_text", m_config.getSpecialConfigValue("input-field", "fail_text", k.c_str())},
|
||||
{"fail_transition", m_config.getSpecialConfigValue("input-field", "fail_transition", k.c_str())},
|
||||
SHADOWABLE("input-field"),
|
||||
}
|
||||
});
|
||||
|
|
|
@ -681,6 +681,8 @@ void CHyprlock::onPasswordCheckTimer() {
|
|||
Debug::log(LOG, "Authentication failed: {}", m_sPasswordState.result->failReason);
|
||||
m_sPasswordState.lastFailReason = m_sPasswordState.result->failReason;
|
||||
m_sPasswordState.passBuffer = "";
|
||||
m_sPasswordState.failedAttempts += 1;
|
||||
Debug::log(LOG, "Failed attempts: {}", m_sPasswordState.failedAttempts);
|
||||
|
||||
for (auto& o : m_vOutputs) {
|
||||
o->sessionLockSurface->render();
|
||||
|
@ -820,6 +822,10 @@ size_t CHyprlock::getPasswordBufferLen() {
|
|||
return m_sPasswordState.passBuffer.length();
|
||||
}
|
||||
|
||||
size_t CHyprlock::getPasswordFailedAttempts() {
|
||||
return m_sPasswordState.failedAttempts;
|
||||
}
|
||||
|
||||
std::shared_ptr<CTimer> CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data) {
|
||||
std::lock_guard<std::mutex> lg(m_sLoopState.timersMutex);
|
||||
const auto T = m_vTimers.emplace_back(std::make_shared<CTimer>(timeout, cb_, data));
|
||||
|
|
|
@ -54,6 +54,7 @@ class CHyprlock {
|
|||
std::optional<std::string> passwordLastFailReason();
|
||||
|
||||
size_t getPasswordBufferLen();
|
||||
size_t getPasswordFailedAttempts();
|
||||
|
||||
ext_session_lock_manager_v1* getSessionLockMgr();
|
||||
ext_session_lock_v1* getSessionLock();
|
||||
|
@ -117,6 +118,7 @@ class CHyprlock {
|
|||
std::string passBuffer = "";
|
||||
std::shared_ptr<CPassword::SVerificationResult> result;
|
||||
std::optional<std::string> lastFailReason;
|
||||
size_t failedAttempts = 0;
|
||||
} m_sPasswordState;
|
||||
|
||||
struct {
|
||||
|
@ -136,4 +138,4 @@ class CHyprlock {
|
|||
std::vector<uint32_t> m_vPressedKeys;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CHyprlock> g_pHyprlock;
|
||||
inline std::unique_ptr<CHyprlock> g_pHyprlock;
|
||||
|
|
|
@ -4,25 +4,35 @@
|
|||
#include <algorithm>
|
||||
|
||||
CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props) : shadow(this, props, viewport_) {
|
||||
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"));
|
||||
outThick = std::any_cast<Hyprlang::INT>(props.at("outline_thickness"));
|
||||
dots.size = std::any_cast<Hyprlang::FLOAT>(props.at("dots_size"));
|
||||
dots.spacing = std::any_cast<Hyprlang::FLOAT>(props.at("dots_spacing"));
|
||||
dots.center = std::any_cast<Hyprlang::INT>(props.at("dots_center"));
|
||||
dots.rounding = std::any_cast<Hyprlang::INT>(props.at("dots_rounding"));
|
||||
fadeOnEmpty = std::any_cast<Hyprlang::INT>(props.at("fade_on_empty"));
|
||||
fadeTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fade_timeout"));
|
||||
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"));
|
||||
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
|
||||
viewport = viewport_;
|
||||
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"));
|
||||
outThick = std::any_cast<Hyprlang::INT>(props.at("outline_thickness"));
|
||||
dots.size = std::any_cast<Hyprlang::FLOAT>(props.at("dots_size"));
|
||||
dots.spacing = std::any_cast<Hyprlang::FLOAT>(props.at("dots_spacing"));
|
||||
dots.center = std::any_cast<Hyprlang::INT>(props.at("dots_center"));
|
||||
dots.rounding = std::any_cast<Hyprlang::INT>(props.at("dots_rounding"));
|
||||
fadeOnEmpty = std::any_cast<Hyprlang::INT>(props.at("fade_on_empty"));
|
||||
fadeTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fade_timeout"));
|
||||
font = std::any_cast<Hyprlang::INT>(props.at("font_color"));
|
||||
hiddenInputState.enabled = std::any_cast<Hyprlang::INT>(props.at("hide_input"));
|
||||
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
|
||||
placeholder.failColor = std::any_cast<Hyprlang::INT>(props.at("fail_color"));
|
||||
placeholder.failTransitionMs = std::any_cast<Hyprlang::INT>(props.at("fail_transition"));
|
||||
configFailText = std::any_cast<Hyprlang::STRING>(props.at("fail_text"));
|
||||
viewport = viewport_;
|
||||
|
||||
pos = posFromHVAlign(viewport, size, pos, std::any_cast<Hyprlang::STRING>(props.at("halign")), std::any_cast<Hyprlang::STRING>(props.at("valign")));
|
||||
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
|
||||
dots.spacing = std::clamp(dots.spacing, 0.f, 1.f);
|
||||
auto POS__ = std::any_cast<Hyprlang::VEC2>(props.at("position"));
|
||||
pos = {POS__.x, POS__.y};
|
||||
configPos = pos;
|
||||
|
||||
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
|
||||
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
|
||||
|
||||
pos = posFromHVAlign(viewport, size, pos, halign, valign);
|
||||
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
|
||||
dots.spacing = std::clamp(dots.spacing, 0.f, 1.f);
|
||||
placeholder.failTransitionMs = std::clamp(placeholder.failTransitionMs, 1, 5000);
|
||||
|
||||
std::string placeholderText = std::any_cast<Hyprlang::STRING>(props.at("placeholder_text"));
|
||||
if (!placeholderText.empty()) {
|
||||
|
@ -38,6 +48,16 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
|
|||
}
|
||||
}
|
||||
|
||||
static void replaceAllFail(std::string& str, const std::string& from, const std::string& to) {
|
||||
if (from.empty())
|
||||
return;
|
||||
size_t pos = 0;
|
||||
while ((pos = str.find(from, pos)) != std::string::npos) {
|
||||
str.replace(pos, from.length(), to);
|
||||
pos += to.length();
|
||||
}
|
||||
}
|
||||
|
||||
static void fadeOutCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
CPasswordInputField* p = (CPasswordInputField*)data;
|
||||
|
||||
|
@ -93,8 +113,10 @@ void CPasswordInputField::updateFade() {
|
|||
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))
|
||||
if ((fade.appearing && fade.a == 1.0) || (!fade.appearing && fade.a == 0.0)) {
|
||||
fade.animated = false;
|
||||
redrawShadow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,8 +152,9 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
|||
CBox inputFieldBox = {pos, size};
|
||||
CBox outerBox = {pos - Vector2D{outThick, outThick}, size + Vector2D{outThick * 2, outThick * 2}};
|
||||
|
||||
if (firstRender) {
|
||||
firstRender = false;
|
||||
if (firstRender || redrawShadow) {
|
||||
firstRender = false;
|
||||
redrawShadow = false;
|
||||
shadow.markShadowDirty();
|
||||
}
|
||||
|
||||
|
@ -140,8 +163,23 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
|||
updateFade();
|
||||
updateDots();
|
||||
updateFailTex();
|
||||
updateOuter();
|
||||
updateHiddenInputState();
|
||||
|
||||
static auto ORIGSIZEX = size.x;
|
||||
static auto ORIGPOS = pos;
|
||||
|
||||
if (placeholder.failAsset && placeholder.failAsset->texture.m_vSize.x > ORIGSIZEX) {
|
||||
if (placeholder.failAsset->texture.m_vSize.x > size.x)
|
||||
redrawShadow = true;
|
||||
|
||||
size.x = placeholder.failAsset->texture.m_vSize.x + inputFieldBox.h;
|
||||
pos = posFromHVAlign(viewport, size, configPos, halign, valign);
|
||||
} else {
|
||||
size.x = ORIGSIZEX;
|
||||
pos = ORIGPOS;
|
||||
}
|
||||
|
||||
SRenderData shadowData = data;
|
||||
shadowData.opacity *= fade.a;
|
||||
shadow.draw(shadowData);
|
||||
|
@ -241,7 +279,7 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
|||
forceReload = true;
|
||||
}
|
||||
|
||||
return dots.currentAmount != PASSLEN || fade.animated || data.opacity < 1.0 || forceReload;
|
||||
return dots.currentAmount != PASSLEN || fade.animated || outerAnimated || redrawShadow || data.opacity < 1.0 || forceReload;
|
||||
}
|
||||
|
||||
void CPasswordInputField::updateFailTex() {
|
||||
|
@ -255,6 +293,7 @@ void CPasswordInputField::updateFailTex() {
|
|||
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.failAsset);
|
||||
placeholder.failAsset = nullptr;
|
||||
placeholder.failID = "";
|
||||
redrawShadow = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -262,14 +301,18 @@ void CPasswordInputField::updateFailTex() {
|
|||
if (!FAIL.has_value() || !placeholder.canGetNewFail)
|
||||
return;
|
||||
|
||||
placeholder.failText = configFailText;
|
||||
replaceAllFail(placeholder.failText, "$FAIL", FAIL.value());
|
||||
replaceAllFail(placeholder.failText, "$ATTEMPTS", std::to_string(g_pHyprlock->getPasswordFailedAttempts()));
|
||||
|
||||
// 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.asset = placeholder.failText;
|
||||
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["color"] = placeholder.failColor;
|
||||
request.props["font_size"] = (int)size.y / 4;
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
|
||||
|
@ -302,3 +345,68 @@ void CPasswordInputField::updateHiddenInputState() {
|
|||
hiddenInputState.lastColor.a = 1.0;
|
||||
hiddenInputState.lastQuadrant = (hiddenInputState.lastQuadrant + rand() % 3 + 1) % 4;
|
||||
}
|
||||
|
||||
void CPasswordInputField::updateOuter() {
|
||||
if (outThick == 0)
|
||||
return;
|
||||
|
||||
static auto OUTERCOL = outer;
|
||||
static auto TIMER = std::chrono::system_clock::now();
|
||||
bool changeToOuter = placeholder.failID.empty();
|
||||
|
||||
outerAnimated = false;
|
||||
|
||||
if (changeToOuter) {
|
||||
if (outer == OUTERCOL)
|
||||
return;
|
||||
|
||||
if (outer == placeholder.failColor)
|
||||
TIMER = std::chrono::system_clock::now();
|
||||
} else if (!changeToOuter) {
|
||||
if (fade.animated || fade.a < 1.0)
|
||||
changeToOuter = true;
|
||||
|
||||
if (outer == OUTERCOL)
|
||||
TIMER = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
const auto MULTI = std::clamp(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - TIMER).count() / (double)placeholder.failTransitionMs, 0.001, 0.5);
|
||||
const auto DELTA = changeToOuter ? OUTERCOL - placeholder.failColor : placeholder.failColor - OUTERCOL;
|
||||
const auto TARGET = changeToOuter ? OUTERCOL : placeholder.failColor;
|
||||
const auto SOURCE = changeToOuter ? placeholder.failColor : OUTERCOL;
|
||||
|
||||
if (outer.r != TARGET.r) {
|
||||
outer.r += DELTA.r * MULTI;
|
||||
outerAnimated = true;
|
||||
|
||||
if ((SOURCE.r < TARGET.r && outer.r > TARGET.r) || (SOURCE.r > TARGET.r && outer.r < TARGET.r))
|
||||
outer.r = TARGET.r;
|
||||
}
|
||||
|
||||
if (outer.g != TARGET.g) {
|
||||
outer.g += DELTA.g * MULTI;
|
||||
outerAnimated = true;
|
||||
|
||||
if ((SOURCE.g < TARGET.g && outer.g > TARGET.g) || (SOURCE.g > TARGET.g && outer.g < TARGET.g))
|
||||
outer.g = TARGET.g;
|
||||
}
|
||||
|
||||
if (outer.b != TARGET.b) {
|
||||
outer.b += DELTA.b * MULTI;
|
||||
outerAnimated = true;
|
||||
|
||||
if ((SOURCE.b < TARGET.b && outer.b > TARGET.b) || (SOURCE.b > TARGET.b && outer.b < TARGET.b))
|
||||
outer.b = TARGET.b;
|
||||
}
|
||||
|
||||
if (outer.a != TARGET.a) {
|
||||
outer.a += DELTA.a * MULTI;
|
||||
outerAnimated = true;
|
||||
|
||||
if ((SOURCE.a < TARGET.a && outer.a > TARGET.a) || (SOURCE.a > TARGET.a && outer.a < TARGET.a))
|
||||
outer.a = TARGET.a;
|
||||
}
|
||||
|
||||
TIMER = std::chrono::system_clock::now();
|
||||
}
|
||||
|
|
|
@ -20,20 +20,26 @@ class CPasswordInputField : public IWidget {
|
|||
void onFadeOutTimer();
|
||||
|
||||
private:
|
||||
void updateDots();
|
||||
void updateFade();
|
||||
void updateFailTex();
|
||||
void updateHiddenInputState();
|
||||
void updateDots();
|
||||
void updateFade();
|
||||
void updateFailTex();
|
||||
void updateHiddenInputState();
|
||||
void updateOuter();
|
||||
|
||||
bool firstRender = true;
|
||||
bool firstRender = true;
|
||||
bool redrawShadow = false;
|
||||
bool outerAnimated = false;
|
||||
|
||||
Vector2D size;
|
||||
Vector2D pos;
|
||||
Vector2D viewport;
|
||||
Vector2D size;
|
||||
Vector2D pos;
|
||||
Vector2D viewport;
|
||||
Vector2D configPos;
|
||||
|
||||
int outThick, rounding;
|
||||
std::string halign, valign, configFailText;
|
||||
|
||||
CColor inner, outer, font;
|
||||
int outThick, rounding;
|
||||
|
||||
CColor inner, outer, font;
|
||||
|
||||
struct {
|
||||
float currentAmount = 0;
|
||||
|
@ -61,6 +67,9 @@ class CPasswordInputField : public IWidget {
|
|||
std::string failID = "";
|
||||
SPreloadedAsset* failAsset = nullptr;
|
||||
bool canGetNewFail = true;
|
||||
CColor failColor;
|
||||
int failTransitionMs = 0;
|
||||
std::string failText = "";
|
||||
} placeholder;
|
||||
|
||||
struct {
|
||||
|
|
Loading…
Reference in a new issue