mirror of
https://github.com/hyprwm/hyprlock.git
synced 2024-12-22 13:29:48 +01:00
widgets: add label
This commit is contained in:
parent
dfd2f851da
commit
e207d34b88
12 changed files with 319 additions and 23 deletions
|
@ -33,7 +33,7 @@ message(STATUS "Checking deps...")
|
|||
find_package(Threads REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon cairo pango pam)
|
||||
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon cairo pangocairo pam)
|
||||
|
||||
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
|
||||
add_executable(hyprlock ${SRCFILES})
|
||||
|
|
|
@ -33,6 +33,16 @@ void CConfigManager::init() {
|
|||
m_config.addSpecialConfigValue("input-field", "outline_thickness", Hyprlang::INT{4});
|
||||
m_config.addSpecialConfigValue("input-field", "fade_on_empty", Hyprlang::INT{1});
|
||||
|
||||
m_config.addSpecialCategory("label", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
|
||||
m_config.addSpecialConfigValue("label", "monitor", Hyprlang::STRING{""});
|
||||
m_config.addSpecialConfigValue("label", "position", Hyprlang::VEC2{400, 90});
|
||||
m_config.addSpecialConfigValue("label", "color", Hyprlang::INT{0xFFFFFFFF});
|
||||
m_config.addSpecialConfigValue("label", "font_size", Hyprlang::INT{16});
|
||||
m_config.addSpecialConfigValue("label", "text", Hyprlang::STRING{"Sample Text"});
|
||||
m_config.addSpecialConfigValue("label", "font_family", Hyprlang::STRING{"Sans"});
|
||||
m_config.addSpecialConfigValue("label", "halign", Hyprlang::STRING{"none"});
|
||||
m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"none"});
|
||||
|
||||
m_config.commence();
|
||||
|
||||
auto result = m_config.parse();
|
||||
|
@ -82,5 +92,24 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
// clang-format on
|
||||
}
|
||||
|
||||
keys = m_config.listKeysForSpecialCategory("label");
|
||||
for (auto& k : keys) {
|
||||
// clang-format off
|
||||
result.push_back(CConfigManager::SWidgetConfig{
|
||||
"label",
|
||||
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("label", "monitor", k.c_str())),
|
||||
{
|
||||
{"position", m_config.getSpecialConfigValue("label", "position", k.c_str())},
|
||||
{"color", m_config.getSpecialConfigValue("label", "color", k.c_str())},
|
||||
{"font_size", m_config.getSpecialConfigValue("label", "font_size", k.c_str())},
|
||||
{"font_family", m_config.getSpecialConfigValue("label", "font_family", k.c_str())},
|
||||
{"text", m_config.getSpecialConfigValue("label", "text", k.c_str())},
|
||||
{"halign", m_config.getSpecialConfigValue("label", "halign", k.c_str())},
|
||||
{"valign", m_config.getSpecialConfigValue("label", "valign", k.c_str())},
|
||||
}
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -2,19 +2,40 @@
|
|||
#include "../config/ConfigManager.hpp"
|
||||
#include "../core/Egl.hpp"
|
||||
#include <cairo/cairo.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <algorithm>
|
||||
#include "../core/hyprlock.hpp"
|
||||
|
||||
std::mutex cvmtx;
|
||||
|
||||
CAsyncResourceGatherer::CAsyncResourceGatherer() {
|
||||
thread = std::thread([this]() { this->gather(); });
|
||||
thread.detach();
|
||||
initThread = std::thread([this]() {
|
||||
this->gather();
|
||||
this->asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); });
|
||||
this->asyncLoopThread.detach();
|
||||
});
|
||||
initThread.detach();
|
||||
}
|
||||
|
||||
SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
|
||||
if (asyncLoopState.busy)
|
||||
return nullptr;
|
||||
|
||||
std::lock_guard<std::mutex> lg(asyncLoopState.loopMutex);
|
||||
|
||||
for (auto& a : assets) {
|
||||
if (a.first == id)
|
||||
return &a.second;
|
||||
}
|
||||
|
||||
if (!preloadTargets.empty()) {
|
||||
apply();
|
||||
for (auto& a : assets) {
|
||||
if (a.first == id)
|
||||
return &a.second;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -85,8 +106,107 @@ void CAsyncResourceGatherer::apply() {
|
|||
|
||||
cairo_destroy((cairo_t*)t.cairo);
|
||||
cairo_surface_destroy((cairo_surface_t*)t.cairosurface);
|
||||
} else {
|
||||
Debug::log(ERR, "Unsupported type in ::apply() {}", (int)t.type);
|
||||
}
|
||||
}
|
||||
|
||||
preloadTargets.clear();
|
||||
|
||||
applied = true;
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
|
||||
SPreloadTarget target;
|
||||
target.type = TARGET_IMAGE; /* text is just an image lol */
|
||||
target.id = rq.id;
|
||||
|
||||
const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast<int>(rq.props.at("font_size")) : 16;
|
||||
const CColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CColor>(rq.props.at("color")) : CColor(1.0, 1.0, 1.0, 1.0);
|
||||
const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast<std::string>(rq.props.at("font_family")) : "Sans";
|
||||
|
||||
auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */);
|
||||
auto CAIRO = cairo_create(CAIROSURFACE);
|
||||
|
||||
// draw title using Pango
|
||||
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
|
||||
pango_layout_set_text(layout, rq.asset.c_str(), -1);
|
||||
|
||||
PangoFontDescription* fontDesc = pango_font_description_from_string(FONTFAMILY.c_str());
|
||||
pango_font_description_set_size(fontDesc, FONTSIZE * PANGO_SCALE);
|
||||
pango_layout_set_font_description(layout, fontDesc);
|
||||
pango_font_description_free(fontDesc);
|
||||
int layoutWidth, layoutHeight;
|
||||
pango_layout_get_size(layout, &layoutWidth, &layoutHeight);
|
||||
|
||||
// TODO: avoid this?
|
||||
cairo_destroy(CAIRO);
|
||||
cairo_surface_destroy(CAIROSURFACE);
|
||||
CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE);
|
||||
CAIRO = cairo_create(CAIROSURFACE);
|
||||
|
||||
// clear the pixmap
|
||||
cairo_save(CAIRO);
|
||||
cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);
|
||||
cairo_paint(CAIRO);
|
||||
cairo_restore(CAIRO);
|
||||
|
||||
// render the thing
|
||||
cairo_set_source_rgba(CAIRO, FONTCOLOR.r, FONTCOLOR.g, FONTCOLOR.b, FONTCOLOR.a);
|
||||
|
||||
cairo_move_to(CAIRO, 0, 0);
|
||||
pango_cairo_show_layout(CAIRO, layout);
|
||||
|
||||
g_object_unref(layout);
|
||||
|
||||
cairo_surface_flush(CAIROSURFACE);
|
||||
|
||||
target.cairo = CAIRO;
|
||||
target.cairosurface = CAIROSURFACE;
|
||||
target.data = cairo_image_surface_get_data(CAIROSURFACE);
|
||||
target.size = {layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE};
|
||||
|
||||
preloadTargets.push_back(target);
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::asyncAssetSpinLock() {
|
||||
while (!g_pHyprlock->m_bTerminate) {
|
||||
|
||||
std::unique_lock lk(cvmtx);
|
||||
if (asyncLoopState.pending == false) // avoid a lock if a thread managed to request something already since we .unlock()ed
|
||||
asyncLoopState.loopGuard.wait_for(lk, std::chrono::seconds(5), [this] { return asyncLoopState.pending; }); // wait for events
|
||||
|
||||
asyncLoopState.requestMutex.lock();
|
||||
|
||||
if (asyncLoopState.requests.empty()) {
|
||||
asyncLoopState.requestMutex.unlock();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto requests = asyncLoopState.requests;
|
||||
asyncLoopState.requests.clear();
|
||||
|
||||
asyncLoopState.requestMutex.unlock();
|
||||
|
||||
// process requests
|
||||
|
||||
asyncLoopState.busy = true;
|
||||
for (auto& r : requests) {
|
||||
if (r.type == TARGET_TEXT) {
|
||||
renderText(r);
|
||||
} else {
|
||||
Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type);
|
||||
}
|
||||
}
|
||||
|
||||
asyncLoopState.busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) {
|
||||
std::lock_guard<std::mutex> lg(asyncLoopState.requestMutex);
|
||||
asyncLoopState.requests.push_back(request);
|
||||
std::unique_lock lk(cvmtx);
|
||||
asyncLoopState.pending = true;
|
||||
asyncLoopState.loopGuard.notify_all();
|
||||
}
|
|
@ -8,6 +8,8 @@
|
|||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <condition_variable>
|
||||
#include <any>
|
||||
|
||||
struct SPreloadedAsset {
|
||||
CTexture texture;
|
||||
|
@ -21,17 +23,45 @@ class CAsyncResourceGatherer {
|
|||
|
||||
std::atomic<float> progress = 0;
|
||||
|
||||
SPreloadedAsset* getAssetByID(const std::string& id);
|
||||
/* only call from ogl thread */
|
||||
SPreloadedAsset* getAssetByID(const std::string& id);
|
||||
|
||||
void apply();
|
||||
|
||||
private:
|
||||
std::thread thread;
|
||||
void apply();
|
||||
|
||||
enum eTargetType {
|
||||
TARGET_IMAGE = 0,
|
||||
TARGET_TEXT
|
||||
};
|
||||
|
||||
struct SPreloadRequest {
|
||||
eTargetType type;
|
||||
std::string asset;
|
||||
std::string id;
|
||||
|
||||
std::unordered_map<std::string, std::any> props;
|
||||
};
|
||||
|
||||
void requestAsyncAssetPreload(const SPreloadRequest& request);
|
||||
|
||||
private:
|
||||
std::thread initThread;
|
||||
std::thread asyncLoopThread;
|
||||
|
||||
void asyncAssetSpinLock();
|
||||
void renderText(const SPreloadRequest& rq);
|
||||
|
||||
struct {
|
||||
std::condition_variable loopGuard;
|
||||
std::mutex loopMutex;
|
||||
|
||||
std::mutex requestMutex;
|
||||
|
||||
std::vector<SPreloadRequest> requests;
|
||||
bool pending = false;
|
||||
|
||||
bool busy = false;
|
||||
} asyncLoopState;
|
||||
|
||||
struct SPreloadTarget {
|
||||
eTargetType type = TARGET_IMAGE;
|
||||
std::string id = "";
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "widgets/PasswordInputField.hpp"
|
||||
#include "widgets/Background.hpp"
|
||||
#include "widgets/Label.hpp"
|
||||
|
||||
inline const float fullVerts[] = {
|
||||
1, 0, // top right
|
||||
|
@ -131,6 +132,10 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
|
|||
|
||||
SRenderFeedback feedback;
|
||||
|
||||
const float bga = asyncResourceGatherer->applied ?
|
||||
std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - gatheredAt).count() / 500000.0, 0.0, 1.0) :
|
||||
0.0;
|
||||
|
||||
if (!asyncResourceGatherer->ready) {
|
||||
// render status
|
||||
if (!**PDISABLEBAR) {
|
||||
|
@ -147,7 +152,7 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
|
|||
// render widgets
|
||||
const auto WIDGETS = getOrCreateWidgetsFor(&surf);
|
||||
for (auto& w : *WIDGETS) {
|
||||
feedback.needsFrame = w->draw() || feedback.needsFrame;
|
||||
feedback.needsFrame = w->draw({bga}) || feedback.needsFrame;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +160,7 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
|
|||
|
||||
Debug::log(TRACE, "frame {}", frames);
|
||||
|
||||
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->ready;
|
||||
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->ready || bga < 1.0;
|
||||
|
||||
return feedback;
|
||||
}
|
||||
|
@ -248,11 +253,13 @@ std::vector<std::unique_ptr<IWidget>>* CRenderer::getOrCreateWidgetsFor(const CS
|
|||
// by type
|
||||
if (c.type == "background")
|
||||
widgets[surf].emplace_back(std::make_unique<CBackground>(surf->size, std::string{"background:"} + std::any_cast<Hyprlang::STRING>(c.values.at("path"))));
|
||||
if (c.type == "input-field") {
|
||||
else if (c.type == "input-field") {
|
||||
const auto SIZE = std::any_cast<Hyprlang::VEC2>(c.values.at("size"));
|
||||
widgets[surf].emplace_back(std::make_unique<CPasswordInputField>(
|
||||
surf->size, Vector2D{SIZE.x, SIZE.y}, std::any_cast<Hyprlang::INT>(c.values.at("outer_color")), std::any_cast<Hyprlang::INT>(c.values.at("inner_color")),
|
||||
std::any_cast<Hyprlang::INT>(c.values.at("outline_thickness")), std::any_cast<Hyprlang::INT>(c.values.at("fade_on_empty"))));
|
||||
} else if (c.type == "label") {
|
||||
widgets[surf].emplace_back(std::make_unique<CLabel>(surf->size, c.values));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,17 +5,16 @@ CBackground::CBackground(const Vector2D& viewport_, const std::string& resourceI
|
|||
;
|
||||
}
|
||||
|
||||
bool CBackground::draw() {
|
||||
bool CBackground::draw(const SRenderData& data) {
|
||||
if (!asset)
|
||||
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
|
||||
|
||||
if (!asset)
|
||||
return false;
|
||||
|
||||
float bga = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - g_pRenderer->gatheredAt).count() / 500000.0, 0.0, 1.0);
|
||||
CBox monbox = {0, 0, viewport.x, viewport.y};
|
||||
CBox monbox = {0, 0, viewport.x, viewport.y};
|
||||
|
||||
g_pRenderer->renderTexture(monbox, asset->texture, bga);
|
||||
g_pRenderer->renderTexture(monbox, asset->texture, data.opacity);
|
||||
|
||||
return bga < 1.0;
|
||||
return data.opacity < 1.0;
|
||||
}
|
|
@ -10,7 +10,7 @@ class CBackground : public IWidget {
|
|||
public:
|
||||
CBackground(const Vector2D& viewport, const std::string& resourceID);
|
||||
|
||||
virtual bool draw();
|
||||
virtual bool draw(const SRenderData& data);
|
||||
|
||||
private:
|
||||
Vector2D viewport;
|
||||
|
|
|
@ -2,5 +2,9 @@
|
|||
|
||||
class IWidget {
|
||||
public:
|
||||
virtual bool draw() = 0;
|
||||
struct SRenderData {
|
||||
float opacity = 1;
|
||||
};
|
||||
|
||||
virtual bool draw(const SRenderData& data) = 0;
|
||||
};
|
81
src/renderer/widgets/Label.cpp
Normal file
81
src/renderer/widgets/Label.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include "Label.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include <hyprlang.hpp>
|
||||
#include "../Renderer.hpp"
|
||||
#include "../../helpers/Log.hpp"
|
||||
|
||||
void replaceAll(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();
|
||||
}
|
||||
}
|
||||
|
||||
std::string CLabel::formatString(std::string in) {
|
||||
replaceAll(in, "$USER", std::string{getlogin()});
|
||||
return in;
|
||||
}
|
||||
|
||||
CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props) {
|
||||
std::string labelPreFormat = std::any_cast<Hyprlang::STRING>(props.at("text"));
|
||||
std::string fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
|
||||
CColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
|
||||
int fontSize = std::any_cast<Hyprlang::INT>(props.at("font_size"));
|
||||
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
request.id = std::string{"label:"} + std::to_string((uintptr_t)this);
|
||||
resourceID = request.id;
|
||||
request.asset = formatString(labelPreFormat);
|
||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
||||
request.props["font_family"] = fontFamily;
|
||||
request.props["color"] = labelColor;
|
||||
request.props["font_size"] = fontSize;
|
||||
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
|
||||
auto POS__ = std::any_cast<Hyprlang::VEC2>(props.at("position"));
|
||||
pos = {POS__.x, POS__.y};
|
||||
|
||||
viewport = viewport_;
|
||||
label = request.asset;
|
||||
|
||||
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
|
||||
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
|
||||
}
|
||||
|
||||
bool CLabel::draw(const SRenderData& data) {
|
||||
if (!asset) {
|
||||
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
|
||||
|
||||
if (!asset)
|
||||
return true;
|
||||
|
||||
// calc pos
|
||||
if (halign == "center")
|
||||
pos.x += viewport.x / 2.0 - asset->texture.m_vSize.x / 2.0;
|
||||
else if (halign == "left")
|
||||
pos.x += 0;
|
||||
else if (halign == "right")
|
||||
pos.x += viewport.x - asset->texture.m_vSize.x;
|
||||
else if (halign != "none")
|
||||
Debug::log(ERR, "Label: invalid halign {}", halign);
|
||||
|
||||
if (valign == "center")
|
||||
pos.y += viewport.y / 2.0 - asset->texture.m_vSize.y / 2.0;
|
||||
else if (valign == "top")
|
||||
pos.y += viewport.y - asset->texture.m_vSize.y;
|
||||
else if (valign == "bottom")
|
||||
pos.y += asset->texture.m_vSize.y;
|
||||
else if (valign != "none")
|
||||
Debug::log(ERR, "Label: invalid halign {}", halign);
|
||||
}
|
||||
|
||||
CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y};
|
||||
|
||||
g_pRenderer->renderTexture(box, asset->texture, data.opacity);
|
||||
|
||||
return false;
|
||||
}
|
26
src/renderer/widgets/Label.hpp
Normal file
26
src/renderer/widgets/Label.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "IWidget.hpp"
|
||||
#include "../../helpers/Vector2D.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <any>
|
||||
|
||||
struct SPreloadedAsset;
|
||||
|
||||
class CLabel : public IWidget {
|
||||
public:
|
||||
CLabel(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props);
|
||||
|
||||
virtual bool draw(const SRenderData& data);
|
||||
|
||||
private:
|
||||
std::string formatString(std::string in);
|
||||
|
||||
Vector2D viewport;
|
||||
Vector2D pos;
|
||||
std::string resourceID;
|
||||
std::string label;
|
||||
std::string halign, valign;
|
||||
SPreloadedAsset* asset = nullptr;
|
||||
};
|
|
@ -72,7 +72,7 @@ void CPasswordInputField::updateDots() {
|
|||
std::erase_if(dots, [](const auto& dot) { return !dot.appearing && dot.a == 0.0; });
|
||||
}
|
||||
|
||||
bool CPasswordInputField::draw() {
|
||||
bool CPasswordInputField::draw(const SRenderData& data) {
|
||||
CBox inputFieldBox = {pos, size};
|
||||
CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}};
|
||||
|
||||
|
@ -80,9 +80,9 @@ bool CPasswordInputField::draw() {
|
|||
updateDots();
|
||||
|
||||
CColor outerCol = outer;
|
||||
outer.a = fade.a;
|
||||
outer.a = fade.a * data.opacity;
|
||||
CColor innerCol = inner;
|
||||
innerCol.a = fade.a;
|
||||
innerCol.a = fade.a * data.opacity;
|
||||
|
||||
g_pRenderer->renderRect(outerBox, outerCol, outerBox.h / 2.0);
|
||||
g_pRenderer->renderRect(inputFieldBox, innerCol, inputFieldBox.h / 2.0);
|
||||
|
@ -93,7 +93,7 @@ bool CPasswordInputField::draw() {
|
|||
for (size_t i = 0; i < dots.size(); ++i) {
|
||||
Vector2D currentPos = inputFieldBox.pos() + Vector2D{PASS_SPACING, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * dots[i].idx, 0};
|
||||
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
|
||||
g_pRenderer->renderRect(box, CColor{0, 0, 0, dots[i].a}, PASS_SIZE / 2.0);
|
||||
g_pRenderer->renderRect(box, CColor{0, 0, 0, dots[i].a * data.opacity}, PASS_SIZE / 2.0);
|
||||
}
|
||||
|
||||
return std::ranges::any_of(dots.begin(), dots.end(), [](const auto& dot) { return dot.animated; }) || fade.animated;
|
||||
|
|
|
@ -10,7 +10,7 @@ class CPasswordInputField : public IWidget {
|
|||
public:
|
||||
CPasswordInputField(const Vector2D& viewport, const Vector2D& size, const CColor& outer, const CColor& inner, int out_thick, bool fade_empty);
|
||||
|
||||
virtual bool draw();
|
||||
virtual bool draw(const SRenderData& data);
|
||||
|
||||
private:
|
||||
void updateDots();
|
||||
|
|
Loading…
Reference in a new issue