Compare commits

...

5 commits
v0.8.2 ... main

Author SHA1 Message Date
Max Woolf
f7f0c9c6b0
example: Add config location hint to help new users (#771)
Some checks failed
Build / nix (push) Has been cancelled
2025-05-09 18:54:54 +02:00
nyx
c12cf8e509
core: disable fade in when using --immediate (#763)
Some checks failed
Build / nix (push) Has been cancelled
2025-05-07 07:13:41 +00:00
Virt
0c5fd97d61
renderer: properly treat monitor desc: prefix (#765) 2025-05-07 07:12:58 +00:00
Maximilian Seidler
fae1c4f6fe
misc: readme cleanup, remove deps required by hyprgraphics (#762)
Some checks failed
Build / nix (push) Has been cancelled
2025-05-05 23:45:32 +02:00
Maximilian Seidler
e3bd47e177
widgets: add onclick feature (#736)
Some checks are pending
Build / nix (push) Waiting to run
* widget: add click handling and point containment methods to IWidget interface

* core: add onClick method to handle mouse click events

- renderer: move getOrCreateWidgetsFor method declaration to public section

* core: update mouse event handling to track mouse location and button clicks

* widget: add onclick command handling and point containment to CLabel

- config: add onclick special config value to label

* assets: add label configuration for keyboard layout switching

* config: add onclick configuration for label widgets

 - add CLICKABLE macro for onclick configuration
 - replace direct onclick assignment with CLICKABLE macro

* core: fix cursor shape initialization and pointer handling

 - ensure pointer is available before setting cursor shape
 - initialize cursor shape device if not already done

* core: add hover handling and cursor shape updates

 - implement onHover method to manage widget hover states
 - update cursor shape based on hover status
 - ensure all outputs are redrawn after state changes

* widgets: add hover state management and bounding box calculations

 - add setHover and isHovered methods to manage hover state
 - implement containsPoint method for hit testing
 - override getBoundingBox in CLabel for accurate positioning
 - add onHover method in CLabel to change cursor shape

* core: add hover handling in pointer motion

 - invoke onHover method with current mouse location

* widgets: add hover handling and bounding box for password input field

 - add getBoundingBox method to calculate the widget's bounding box
 - implement onHover method to update cursor shape on hover

* widgets: update hover behavior for label widget

 - modify cursor shape setting to only apply when onclickCommand is not empty

* core: optimize hover handling and rendering for lock surfaces

 - Improve hover state tracking for widgets
 - reduce unnecessary redraw calls by tracking hover changes
 - remove redundant renderAllOutputs() call

* widgets: add onclick and hover to shape and image

* core: trigger hover and onclick only for the currently focused surface

* core: handle fractionalScale in onclick and hover

* core: don't trigger onclick or hover when hide_cursor is set

* misc: remove braces

* core: run onclick commands asnychronously

---------

Co-authored-by: Memoraike <memoraike@gmail.com>
2025-05-05 15:11:24 +00:00
30 changed files with 338 additions and 106 deletions

View file

@ -82,9 +82,6 @@ pkg_check_modules(
hyprlang>=0.6.0
egl
xkbcommon
libjpeg
libwebp
libmagic
cairo
pangocairo
libdrm

View file

@ -2,10 +2,14 @@
Hyprland's simple, yet multi-threaded and GPU-accelerated screen locking utility.
## Features
- uses the secure ext-session-lock protocol
- full support for fractional-scale
- fully GPU accelerated
- multi-threaded resource acquisition for no hitches
- Uses the ext-session-lock protocol
- Support for fractional-scale
- Fully GPU accelerated
- Multi-threaded resource acquisition
- Blurred screenshot as the background
- Native fingerprint support (using libfprint's dbus interface)
- Some of Hyprland's eyecandy: gradient borders, blur, animations, shadows, etc.
- and more...
## How it looks
@ -25,26 +29,23 @@ yay -S hyprlock-git # compiles from latest source
### Deps
You need the following dependencies
- wayland-client
- wayland-protocols
- mesa
- hyprwayland-scanner
And the development libraries for the following
- cairo
- libdrm
- pango
- xkbcommon
- pam
- hyprgraphics
- hyprland-protocols
- hyprlang
- hyprutils
- hyprgraphics
- libmagic (file-devel on Fedora)
- hyprwayland-scanner
- mesa (required is libgbm, libdrm and the opengl runtime)
- pam
- pango
- sdbus-cpp (>= 2.0.0)
- wayland-client
- wayland-protocols
- xkbcommon
Development libraries are usually suffixed with `-devel` or `-dev` in most distro repos.
You also need to install `mesa-libgbm-devel` on some distros like RPM based ones where its not
bundled with the mesa package.
Sometimes distro packages are missing required development files.
Such distros usually offer development versions of library package - commonly suffixed with `-devel` or `-dev`.
### Building

View file

@ -5,11 +5,14 @@
# ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#general-remarks
#
# shortcuts to clear password buffer: ESC, Ctrl+U, Ctrl+Backspace
#
# you can get started by copying this config to ~/.config/hypr/hyprlock.conf
#
$font = Monospace
general {
hide_cursor = true
hide_cursor = false
}
# uncomment to enable fingerprint authentication
@ -90,3 +93,14 @@ label {
halign = right
valign = top
}
label {
monitor =
text = $LAYOUT[en,ru]
font_size = 24
onclick = hyprctl switchxkblayout all next
position = 250, -20
halign = center
valign = center
}

View file

@ -4,11 +4,8 @@
cmake,
pkg-config,
cairo,
file,
libdrm,
libGL,
libjpeg,
libwebp,
libxkbcommon,
libgbm,
hyprgraphics,
@ -40,11 +37,8 @@ stdenv.mkDerivation {
buildInputs = [
cairo
file
libdrm
libGL
libjpeg
libwebp
libxkbcommon
libgbm
hyprgraphics

View file

@ -209,6 +209,9 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue(name, "shadow_passes", Hyprlang::INT{0}); \
m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \
m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2});
#define CLICKABLE(name) m_config.addSpecialConfigValue(name, "onclick", Hyprlang::STRING{""});
m_config.addConfigValue("general:text_trim", Hyprlang::INT{1});
m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0});
m_config.addConfigValue("general:grace", Hyprlang::INT{0});
@ -257,6 +260,7 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("shape", "xray", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "zindex", Hyprlang::INT{0});
SHADOWABLE("shape");
CLICKABLE("shape");
m_config.addSpecialCategory("image", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("image", "monitor", Hyprlang::STRING{""});
@ -273,6 +277,7 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("image", "reload_cmd", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("image", "zindex", Hyprlang::INT{0});
SHADOWABLE("image");
CLICKABLE("image");
m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""});
@ -320,6 +325,7 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("label", "text_align", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("label", "zindex", Hyprlang::INT{0});
SHADOWABLE("label");
CLICKABLE("label");
m_config.registerHandler(&::handleSource, "source", {.allowFlags = false});
m_config.registerHandler(&::handleBezier, "bezier", {.allowFlags = false});
@ -356,6 +362,7 @@ void CConfigManager::init() {
Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
#undef SHADOWABLE
#undef CLICKABLE
}
std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
@ -367,6 +374,8 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
"shadow_boost", m_config.getSpecialConfigValue(name, "shadow_boost", k.c_str()) \
}
#define CLICKABLE(name) {"onclick", m_config.getSpecialConfigValue(name, "onclick", k.c_str())}
//
auto keys = m_config.listKeysForSpecialCategory("background");
result.reserve(keys.size());
@ -414,6 +423,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"xray", m_config.getSpecialConfigValue("shape", "xray", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("shape", "zindex", k.c_str())},
SHADOWABLE("shape"),
CLICKABLE("shape"),
}
});
// clang-format on
@ -440,6 +450,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"reload_cmd", m_config.getSpecialConfigValue("image", "reload_cmd", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("image", "zindex", k.c_str())},
SHADOWABLE("image"),
CLICKABLE("image"),
}
});
// clang-format on
@ -505,6 +516,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"text_align", m_config.getSpecialConfigValue("label", "text_align", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("label", "zindex", k.c_str())},
SHADOWABLE("label"),
CLICKABLE("label"),
}
});
// clang-format on

View file

@ -9,9 +9,12 @@ CCursorShape::CCursorShape(SP<CCWpCursorShapeManagerV1> mgr) : mgr(mgr) {
}
void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) {
if (!dev)
if (!g_pSeatManager->m_pPointer)
return;
if (!dev)
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
dev->sendSetShape(lastCursorSerial, shape);
}

View file

@ -139,3 +139,7 @@ void CSessionLockSurface::onCallback() {
render();
}
}
SP<CCWlSurface> CSessionLockSurface::getWlSurface() {
return surface;
}

View file

@ -26,6 +26,7 @@ class CSessionLockSurface {
void render();
void onCallback();
void onScaleUpdate();
SP<CCWlSurface> getWlSurface();
private:
WP<COutput> m_outputRef;

View file

@ -26,7 +26,13 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
if (caps & WL_SEAT_CAPABILITY_POINTER) {
m_pPointer = makeShared<CCWlPointer>(r->sendGetPointer());
static const auto HIDECURSOR = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
if (!*HIDECURSOR)
g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation);
if (std::chrono::system_clock::now() > g_pHyprlock->m_tGraceEnds)
return;
@ -40,16 +46,34 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
if (!m_pCursorShape)
return;
static const auto HIDE = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
m_pCursorShape->lastCursorSerial = serial;
if (*HIDE)
if (*HIDECURSOR)
m_pCursorShape->hideCursor();
else
m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
g_pHyprlock->m_vLastEnterCoords = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
if (*HIDECURSOR)
return;
for (const auto& POUTPUT : g_pHyprlock->m_vOutputs) {
if (!POUTPUT->m_sessionLockSurface)
continue;
const auto& PWLSURFACE = POUTPUT->m_sessionLockSurface->getWlSurface();
if (PWLSURFACE->resource() == surf)
g_pHyprlock->m_focusedOutput = POUTPUT;
}
});
m_pPointer->setLeave([](CCWlPointer* r, uint32_t serial, wl_proxy* surf) { g_pHyprlock->m_focusedOutput.reset(); });
m_pPointer->setButton([](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, wl_pointer_button_state state) {
if (*HIDECURSOR)
return;
g_pHyprlock->onClick(button, state == WL_POINTER_BUTTON_STATE_PRESSED, g_pHyprlock->m_vMouseLocation);
});
}

View file

@ -669,6 +669,61 @@ void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) {
}
}
void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (!m_focusedOutput.lock())
return;
// TODO: add the UNLIKELY marco from Hyprland
if (!m_focusedOutput->m_sessionLockSurface)
return;
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
for (const auto& widget : widgets) {
if (widget->containsPoint(SCALEDPOS))
widget->onClick(button, down, pos);
}
}
void CHyprlock::onHover(const Vector2D& pos) {
if (!m_focusedOutput.lock())
return;
if (!m_focusedOutput->m_sessionLockSurface)
return;
bool outputNeedsRedraw = false;
bool cursorChanged = false;
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
for (const auto& widget : widgets) {
const bool CONTAINSPOINT = widget->containsPoint(SCALEDPOS);
const bool HOVERED = widget->isHovered();
if (CONTAINSPOINT) {
if (!HOVERED) {
widget->setHover(true);
widget->onHover(pos);
outputNeedsRedraw = true;
}
if (!cursorChanged)
cursorChanged = true;
} else if (HOVERED) {
widget->setHover(false);
outputNeedsRedraw = true;
}
}
if (!cursorChanged)
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
if (outputNeedsRedraw)
m_focusedOutput->m_sessionLockSurface->render();
}
bool CHyprlock::acquireSessionLock() {
Debug::log(LOG, "Locking session");
m_sLockState.lock = makeShared<CCExtSessionLockV1>(m_sWaylandState.sessionLock->sendLock());
@ -817,19 +872,6 @@ void CHyprlock::enqueueForceUpdateTimers() {
nullptr, false);
}
std::string CHyprlock::spawnSync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync()) {
Debug::log(ERR, "Failed to run \"{}\"", cmd);
return "";
}
if (!proc.stdErr().empty())
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());
return proc.stdOut();
}
SP<CCZwlrScreencopyManagerV1> CHyprlock::getScreencopy() {
return m_sWaylandState.screencopy;
}

View file

@ -48,9 +48,9 @@ class CHyprlock {
bool acquireSessionLock();
void releaseSessionLock();
std::string spawnSync(const std::string& cmd);
void onKey(uint32_t key, bool down);
void onClick(uint32_t button, bool down, const Vector2D& pos);
void onHover(const Vector2D& pos);
void startKeyRepeat(xkb_keysym_t sym);
void repeatKey(xkb_keysym_t sym);
void handleKeySym(xkb_keysym_t sym, bool compose);
@ -95,6 +95,9 @@ class CHyprlock {
//
std::chrono::system_clock::time_point m_tGraceEnds;
Vector2D m_vLastEnterCoords = {};
WP<COutput> m_focusedOutput;
Vector2D m_vMouseLocation = {};
std::shared_ptr<CTimer> m_pKeyRepeatTimer = nullptr;

View file

@ -5,9 +5,11 @@
#include "MiscFunctions.hpp"
#include "Log.hpp"
#include <hyprutils/string/String.hpp>
#include <hyprutils/os/Process.hpp>
#include <unistd.h>
using namespace Hyprutils::String;
using namespace Hyprutils::OS;
std::string absolutePath(const std::string& rawpath, const std::string& currentDir) {
std::filesystem::path path(rawpath);
@ -137,3 +139,22 @@ int createPoolFile(size_t size, std::string& name) {
return FD;
}
std::string spawnSync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync()) {
Debug::log(ERR, "Failed to run \"{}\"", cmd);
return "";
}
if (!proc.stdErr().empty())
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());
return proc.stdOut();
}
void spawnAsync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runAsync())
Debug::log(ERR, "Failed to start \"{}\"", cmd);
}

View file

@ -7,3 +7,5 @@
std::string absolutePath(const std::string&, const std::string&);
int64_t configStringToInt(const std::string& VALUE);
int createPoolFile(size_t size, std::string& name);
std::string spawnSync(const std::string& cmd);
void spawnAsync(const std::string& cmd);

View file

@ -107,7 +107,7 @@ int main(int argc, char** argv, char** envp) {
return 1;
}
if (noFadeIn)
if (noFadeIn || immediate)
g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default");
try {

View file

@ -2,7 +2,6 @@
#include "../config/ConfigManager.hpp"
#include "../core/Egl.hpp"
#include <cairo/cairo.h>
#include <magic.h>
#include <pango/pangocairo.h>
#include <algorithm>
#include <filesystem>
@ -218,7 +217,7 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;
static const auto TRIM = g_pConfigManager->getValue<Hyprlang::INT>("general:text_trim");
std::string text = ISCMD ? g_pHyprlock->spawnSync(rq.asset) : rq.asset;
std::string text = ISCMD ? spawnSync(rq.asset) : rq.asset;
if (*TRIM) {
text.erase(0, text.find_first_not_of(" \n\r\t"));

View file

@ -117,6 +117,6 @@ CFramebuffer::~CFramebuffer() {
release();
}
bool CFramebuffer::isAllocated() {
bool CFramebuffer::isAllocated() const {
return m_iFb != (GLuint)-1;
}

View file

@ -13,7 +13,7 @@ class CFramebuffer {
void bind() const;
void release();
void reset();
bool isAllocated();
bool isAllocated() const;
Vector2D m_vSize;

View file

@ -406,7 +406,7 @@ std::vector<SP<IWidget>>& CRenderer::getOrCreateWidgetsFor(const CSessionLockSur
const auto POUTPUT = surf.m_outputRef.lock();
for (auto& c : CWIDGETS) {
if (!c.monitor.empty() && c.monitor != POUTPUT->stringPort && !POUTPUT->stringDesc.starts_with(c.monitor) && !POUTPUT->stringDesc.starts_with("desc:" + c.monitor))
if (!c.monitor.empty() && c.monitor != POUTPUT->stringPort && !POUTPUT->stringDesc.starts_with(c.monitor) && !("desc:" + POUTPUT->stringDesc).starts_with(c.monitor))
continue;
// by type

View file

@ -48,12 +48,11 @@ class CRenderer {
void startFadeIn();
void startFadeOut(bool unlock = false, bool immediate = true);
std::vector<SP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf);
private:
widgetMap_t widgets;
std::vector<SP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf);
CShader rectShader;
CShader texShader;
CShader texMixShader;

View file

@ -240,7 +240,7 @@ void CBackground::onReloadTimerUpdate() {
// Path parsing and early returns
if (!reloadCommand.empty()) {
path = g_pHyprlock->spawnSync(reloadCommand);
path = spawnSync(reloadCommand);
if (path.ends_with('\n'))
path.pop_back();

View file

@ -266,3 +266,15 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
result.formatted = in;
return result;
}
void IWidget::setHover(bool hover) {
hovered = hover;
}
bool IWidget::isHovered() const {
return hovered;
}
bool IWidget::containsPoint(const Vector2D& pos) const {
return getBoundingBoxWl().containsPoint(pos);
}

View file

@ -1,7 +1,7 @@
#pragma once
#include "../../helpers/Math.hpp"
#include "../../defines.hpp"
#include "../../helpers/Math.hpp"
#include <string>
#include <unordered_map>
#include <any>
@ -24,6 +24,13 @@ class IWidget {
static int roundingForBox(const CBox& box, int roundingConfig);
static int roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness);
virtual CBox getBoundingBoxWl() const {
return CBox();
};
virtual void onClick(uint32_t button, bool down, const Vector2D& pos) {}
virtual void onHover(const Vector2D& pos) {}
bool containsPoint(const Vector2D& pos) const;
struct SFormatResult {
std::string formatted;
float updateEveryMs = 0; // 0 means don't (static)
@ -33,4 +40,10 @@ class IWidget {
};
static SFormatResult formatString(std::string in);
void setHover(bool hover);
bool isHovered() const;
private:
bool hovered = false;
};

View file

@ -6,6 +6,7 @@
#include "../../config/ConfigDataValues.hpp"
#include <cmath>
#include <hyprlang.hpp>
#include <hyprutils/math/Vector2D.hpp>
CImage::~CImage() {
reset();
@ -31,7 +32,7 @@ void CImage::onTimerUpdate() {
const std::string OLDPATH = path;
if (!reloadCommand.empty()) {
path = g_pHyprlock->spawnSync(reloadCommand);
path = spawnSync(reloadCommand);
if (path.ends_with('\n'))
path.pop_back();
@ -88,7 +89,7 @@ void CImage::configure(const std::unordered_map<std::string, std::any>& props, c
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = *CGradientValueData::fromAnyPv(props.at("border_color"));
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
@ -96,6 +97,7 @@ void CImage::configure(const std::unordered_map<std::string, std::any>& props, c
path = std::any_cast<Hyprlang::STRING>(props.at("path"));
reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time"));
reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd"));
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CImage: {}", e.what()); //
} catch (const std::out_of_range& e) {
@ -196,10 +198,10 @@ bool CImage::draw(const SRenderData& data) {
shadow.draw(data);
const auto TEXPOS = posFromHVAlign(viewport, tex->m_vSize, pos, halign, valign, angle);
pos = posFromHVAlign(viewport, tex->m_vSize, configPos, halign, valign, angle);
texbox.x = TEXPOS.x;
texbox.y = TEXPOS.y;
texbox.x = pos.x;
texbox.y = pos.y;
texbox.round();
texbox.rot = angle;
@ -233,3 +235,23 @@ void CImage::renderUpdate() {
g_pHyprlock->renderOutput(stringPort);
}
CBox CImage::getBoundingBoxWl() const {
if (!imageFB.isAllocated())
return CBox{};
return {
Vector2D{pos.x, viewport.y - pos.y - imageFB.m_cTex.m_vSize.y},
imageFB.m_cTex.m_vSize,
};
}
void CImage::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CImage::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}

View file

@ -24,6 +24,9 @@ class CImage : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
void reset();
@ -42,6 +45,7 @@ class CImage : public IWidget {
double angle;
CGradientValueData color;
Vector2D pos;
Vector2D configPos;
std::string halign, valign, path;
@ -49,6 +53,8 @@ class CImage : public IWidget {
int reloadTime;
std::string reloadCommand;
std::string onclickCommand;
std::filesystem::file_time_type modificationTime;
std::shared_ptr<CTimer> imageTimer;
CAsyncResourceGatherer::SPreloadRequest request;

View file

@ -3,6 +3,7 @@
#include "../../helpers/Log.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../config/ConfigDataValues.hpp"
#include <hyprlang.hpp>
#include <stdexcept>
@ -79,6 +80,7 @@ void CLabel::configure(const std::unordered_map<std::string, std::any>& props, c
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
angle = angle * M_PI / 180.0;
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
std::string textAlign = std::any_cast<Hyprlang::STRING>(props.at("text_align"));
std::string fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
@ -172,3 +174,23 @@ void CLabel::renderUpdate() {
g_pHyprlock->renderOutput(outputStringPort);
}
CBox CLabel::getBoundingBoxWl() const {
if (!asset)
return CBox{};
return {
Vector2D{pos.x, viewport.y - pos.y - asset->texture.m_vSize.y},
asset->texture.m_vSize,
};
}
void CLabel::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CLabel::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}

View file

@ -21,6 +21,9 @@ class CLabel : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
void reset();
@ -43,6 +46,7 @@ class CLabel : public IWidget {
std::string resourceID;
std::string pendingResourceID; // if dynamic label
std::string halign, valign;
std::string onclickCommand;
SPreloadedAsset* asset = nullptr;
std::string outputStringPort;

View file

@ -473,3 +473,14 @@ void CPasswordInputField::updateColors() {
colorState.font = fontTarget;
}
CBox CPasswordInputField::getBoundingBoxWl() const {
return {
Vector2D{pos.x, viewport.y - pos.y - size->value().y},
size->value(),
};
}
void CPasswordInputField::onHover(const Vector2D& pos) {
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT);
}

View file

@ -23,6 +23,8 @@ class CPasswordInputField : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onHover(const Vector2D& pos);
virtual CBox getBoundingBoxWl() const;
void reset();
void onFadeOutTimer();

View file

@ -1,8 +1,11 @@
#include "Shape.hpp"
#include "../Renderer.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include <cmath>
#include <hyprlang.hpp>
#include <sys/types.h>
void CShape::registerSelf(const SP<CShape>& self) {
m_self = self;
@ -24,6 +27,7 @@ void CShape::configure(const std::unordered_map<std::string, std::any>& props, c
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
xray = std::any_cast<Hyprlang::INT>(props.at("xray"));
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CShape: {}", e.what()); //
} catch (const std::out_of_range& e) {
@ -100,3 +104,19 @@ bool CShape::draw(const SRenderData& data) {
return data.opacity < 1.0;
}
CBox CShape::getBoundingBoxWl() const {
return {
Vector2D{pos.x, viewport.y - pos.y - size.y},
size,
};
}
void CShape::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CShape::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}

View file

@ -18,6 +18,9 @@ class CShape : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
private:
WP<CShape> m_self;
@ -38,6 +41,7 @@ class CShape : public IWidget {
std::string halign, valign;
bool firstRender = true;
std::string onclickCommand;
Vector2D viewport;
CShadowable shadow;