diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index b61a0f6..5b09acf 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -850,6 +850,10 @@ bool CHyprlock::getPasswordShow() { return m_sPasswordState.show; } +void CHyprlock::togglePasswordShow() { + m_sPasswordState.show = !m_sPasswordState.show; +} + size_t CHyprlock::getPasswordBufferDisplayLen() { // Counts utf-8 codepoints in the buffer. A byte is counted if it does not match 0b10xxxxxx. return std::count_if(m_sPasswordState.passBuffer.begin(), m_sPasswordState.passBuffer.end(), [](char c) { return (c & 0xc0) != 0x80; }); diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 288f682..658a4c8 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -67,6 +67,8 @@ class CHyprlock { std::string getPasswordBuffer(); bool getPasswordShow(); + void togglePasswordShow(); + SP getSessionLockMgr(); SP getSessionLock(); SP getCompositor(); diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 4278cc4..3d3e8a5 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -103,6 +103,7 @@ void CPasswordInputField::configure(const std::unordered_mapasyncResourceGatherer->getTextAssetSize(request); offset = (inputFieldBox.h - assetSize.y) / 2.0; - if (assetSize.x <= (inputFieldBox.w - offset * 2)) { + // It can be safely assumed that the eye asset is always available + if (!eye.asset || assetSize.x <= (inputFieldBox.w - offset * 2 - eye.margin - eye.asset->texture.m_vSize.x)) { break; } @@ -252,6 +254,22 @@ void CPasswordInputField::renderPasswordUpdate() { g_pHyprlock->renderOutput(outputStringPort); } +void CPasswordInputField::updateEye() { + CAsyncResourceGatherer::SPreloadRequest request; + + std::string textResourceID = std::format("eye:{}", (uintptr_t)this); + request.id = textResourceID; + request.asset = "👁"; + request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; + request.props["font_family"] = fontFamily; + request.props["color"] = colorConfig.font; + request.props["font_size"] = eye.size; + + eye.resourceID = textResourceID; + + g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); +} + bool CPasswordInputField::draw(const SRenderData& data) { if (firstRender || redrawShadow) { firstRender = false; @@ -315,7 +333,10 @@ bool CPasswordInputField::draw(const SRenderData& data) { const int ROUND = roundingForBox(inputFieldBox, rounding); g_pRenderer->renderRect(inputFieldBox, innerCol, ROUND); - if (!hiddenInputState.enabled) { + eye.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(eye.resourceID); + if (!hiddenInputState.enabled && eye.asset) { + Vector2D eyeSize = eye.asset->texture.m_vSize; + if (!showPassword) { const int RECTPASSSIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f; Vector2D passSize{RECTPASSSIZE, RECTPASSSIZE}; @@ -335,7 +356,7 @@ bool CPasswordInputField::draw(const SRenderData& data) { const auto CURRDOTS = dots.currentAmount->value(); const double DOTPAD = (inputFieldBox.h - passSize.y) / 2.0; - const double DOTAREAWIDTH = inputFieldBox.w - (DOTPAD * 2); + const double DOTAREAWIDTH = inputFieldBox.w - (DOTPAD * 2) - eye.margin - eyeSize.x; const int MAXDOTS = std::round(DOTAREAWIDTH * 1.0 / (passSize.x + passSpacing)); const int DOTFLOORED = std::floor(CURRDOTS); const auto DOTALPHA = fontCol.a; @@ -344,7 +365,7 @@ bool CPasswordInputField::draw(const SRenderData& data) { const double CURRWIDTH = ((passSize.x + passSpacing) * CURRDOTS) - passSpacing; // Calculate starting x-position to ensure dots stay centered within the input field - double xstart = dots.center ? ((DOTAREAWIDTH - CURRWIDTH) / 2.0) + DOTPAD : DOTPAD; + double xstart = dots.center ? ((DOTAREAWIDTH - CURRWIDTH) / 2.0) + DOTPAD - (eye.margin - eyeSize.x) / 2 : DOTPAD; if (CURRDOTS > MAXDOTS) xstart = (inputFieldBox.w + MAXDOTS * (passSize.x + passSpacing) - passSpacing - 2 * CURRWIDTH) / 2.0; @@ -379,20 +400,32 @@ bool CPasswordInputField::draw(const SRenderData& data) { g_pRenderer->renderRect(box, fontCol, dots.rounding); fontCol.a = DOTALPHA; + + auto eyeSize = eye.asset->texture.m_vSize; + auto eyePosition = inputFieldBox.pos() + Vector2D{inputFieldBox.w - eyeSize.x - DOTPAD, DOTPAD}; + box = {eyePosition, eyeSize}; + g_pRenderer->renderTexture(box, eye.asset->texture, fontCol.a); } } else if (password.content.length() > 0) { password.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(password.resourceID); if (password.asset) { - auto size = password.asset->texture.m_vSize; - double offset = (inputFieldBox.h - size.y) / 2.0; + auto passSize = password.asset->texture.m_vSize; + auto eyeSize = eye.asset->texture.m_vSize; + double padding = (inputFieldBox.h - passSize.y) / 2.0; - double xstart = password.center ? inputFieldBox.w / 2.0 - size.x / 2.0 : offset; + double xstart = password.center ? (inputFieldBox.w - passSize.x - eyeSize.x + eye.margin) / 2.0 : padding; - Vector2D dotPosition = inputFieldBox.pos() + Vector2D{xstart, offset}; - CBox box{dotPosition, Vector2D(size.x, size.y)}; + Vector2D passwordPosition = inputFieldBox.pos() + Vector2D{xstart, padding}; + CBox box{passwordPosition, passSize}; g_pRenderer->renderTexture(box, password.asset->texture, fontCol.a); + + passwordPosition = inputFieldBox.pos() + Vector2D{inputFieldBox.w - eyeSize.x - padding, padding}; + box = {passwordPosition, eyeSize}; + g_pRenderer->renderTexture(box, eye.asset->texture, fontCol.a); + } else { + forceReload = true; } } } @@ -580,3 +613,21 @@ CBox CPasswordInputField::getBoundingBoxWl() const { void CPasswordInputField::onHover(const Vector2D& pos) { g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT); } + +void CPasswordInputField::onClick(uint32_t button, bool down, const Vector2D& pos) { + + if (!password.asset || !eye.asset || !down) + return; + + CBox inputFieldBox = getBoundingBoxWl(); + auto eyeSize = eye.asset->texture.m_vSize; + auto passwordSize = password.asset->texture.m_vSize; + double offset = (inputFieldBox.h - passwordSize.y) / 2.0; + + Vector2D dotPosition = inputFieldBox.pos() + Vector2D{inputFieldBox.w - offset - eye.margin - eyeSize.x, offset}; + inputFieldBox = {dotPosition, eyeSize}; + + if (inputFieldBox.containsPoint(pos)) { + g_pHyprlock->togglePasswordShow(); + } +} diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index c83bdc0..788eb3a 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -24,6 +24,7 @@ class CPasswordInputField : public IWidget { virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); virtual void onHover(const Vector2D& pos); + virtual void onClick(uint32_t button, bool down, const Vector2D& pos); virtual CBox getBoundingBoxWl() const; void reset(); @@ -35,6 +36,7 @@ class CPasswordInputField : public IWidget { WP m_self; void updatePassword(); + void updateEye(); void updateDots(); void updateFade(); void updatePlaceholder(); @@ -83,6 +85,15 @@ class CPasswordInputField : public IWidget { int trim = 0; } password; + struct { + int size = 16; + std::string resourceID = ""; + std::string placement = "right"; + SPreloadedAsset* asset = nullptr; + + const int margin = 4; + } eye; + struct { PHLANIMVAR a; bool appearing = true;