image: add reload options (#247)

* image: add reload options

* check for actual file changes

* use modtime

* check only same paths

* add Nix HM
This commit is contained in:
bvr-yr 2024-04-07 20:09:25 +03:00 committed by GitHub
parent 071ebcefb9
commit bbbb960e42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 180 additions and 15 deletions

View File

@ -225,6 +225,18 @@ in {
default = 0.0; default = 0.0;
}; };
reload_time = mkOption {
description = "Interval in seconds between reloading the image";
type = int;
default = -1;
};
reload_cmd = mkOption {
description = "Command to obtain new path";
type = str;
default = "";
};
position = { position = {
x = mkOption { x = mkOption {
description = "X position of the image"; description = "X position of the image";
@ -558,6 +570,8 @@ in {
border_size = ${toString image.border_size} border_size = ${toString image.border_size}
border_color = ${image.border_color} border_color = ${image.border_color}
rotate = ${toString image.rotate} rotate = ${toString image.rotate}
reload_time = ${toString image.reload_time}
reload_cmd = ${image.reload_cmd}
position = ${toString image.position.x}, ${toString image.position.y} position = ${toString image.position.x}, ${toString image.position.y}
halign = ${image.halign} halign = ${image.halign}

View File

@ -73,6 +73,8 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("image", "rotate", Hyprlang::FLOAT{0}); m_config.addSpecialConfigValue("image", "rotate", Hyprlang::FLOAT{0});
m_config.addSpecialConfigValue("image", "reload_time", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("image", "reload_cmd", Hyprlang::STRING{""});
SHADOWABLE("image"); SHADOWABLE("image");
m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
@ -184,6 +186,8 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"halign", m_config.getSpecialConfigValue("image", "halign", k.c_str())}, {"halign", m_config.getSpecialConfigValue("image", "halign", k.c_str())},
{"valign", m_config.getSpecialConfigValue("image", "valign", k.c_str())}, {"valign", m_config.getSpecialConfigValue("image", "valign", k.c_str())},
{"rotate", m_config.getSpecialConfigValue("image", "rotate", k.c_str())}, {"rotate", m_config.getSpecialConfigValue("image", "rotate", k.c_str())},
{"reload_time", m_config.getSpecialConfigValue("image", "reload_time", k.c_str())},
{"reload_cmd", m_config.getSpecialConfigValue("image", "reload_cmd", k.c_str())},
SHADOWABLE("image"), SHADOWABLE("image"),
} }
}); });

View File

@ -203,6 +203,25 @@ void CAsyncResourceGatherer::apply() {
applied = true; applied = true;
} }
void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) {
SPreloadTarget target;
target.type = TARGET_IMAGE;
target.id = rq.id;
const auto ABSOLUTEPATH = absolutePath(rq.asset, "");
const auto CAIROISURFACE = cairo_image_surface_create_from_png(ABSOLUTEPATH.c_str());
const auto CAIRO = cairo_create(CAIROISURFACE);
cairo_scale(CAIRO, 1, 1);
target.cairo = CAIRO;
target.cairosurface = CAIROISURFACE;
target.data = cairo_image_surface_get_data(CAIROISURFACE);
target.size = {(double)cairo_image_surface_get_width(CAIROISURFACE), (double)cairo_image_surface_get_height(CAIROISURFACE)};
preloadTargets.push_back(target);
}
void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
SPreloadTarget target; SPreloadTarget target;
target.type = TARGET_IMAGE; /* text is just an image lol */ target.type = TARGET_IMAGE; /* text is just an image lol */
@ -314,6 +333,8 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() {
for (auto& r : requests) { for (auto& r : requests) {
if (r.type == TARGET_TEXT) { if (r.type == TARGET_TEXT) {
renderText(r); renderText(r);
} else if (r.type == TARGET_IMAGE) {
renderImage(r);
} else { } else {
Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type); Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type);
continue; continue;

View File

@ -56,6 +56,7 @@ class CAsyncResourceGatherer {
void asyncAssetSpinLock(); void asyncAssetSpinLock();
void renderText(const SPreloadRequest& rq); void renderText(const SPreloadRequest& rq);
void renderImage(const SPreloadRequest& rq);
struct { struct {
std::condition_variable loopGuard; std::condition_variable loopGuard;
@ -88,4 +89,4 @@ class CAsyncResourceGatherer {
std::unordered_map<std::string, SPreloadedAsset> assets; std::unordered_map<std::string, SPreloadedAsset> assets;
void gather(); void gather();
}; };

View File

@ -1,7 +1,76 @@
#include "Image.hpp" #include "Image.hpp"
#include "../Renderer.hpp" #include "../Renderer.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp"
#include <cmath> #include <cmath>
CImage::~CImage() {
imageTimer->cancel();
imageTimer.reset();
}
static void onTimer(std::shared_ptr<CTimer> self, void* data) {
const auto PIMAGE = (CImage*)data;
PIMAGE->onTimerUpdate();
PIMAGE->plantTimer();
}
static void onAssetCallback(void* data) {
const auto PIMAGE = (CImage*)data;
PIMAGE->renderSuper();
}
void CImage::onTimerUpdate() {
const std::string OLDPATH = path;
if (!reloadCommand.empty()) {
path = g_pHyprlock->spawnSync(reloadCommand);
if (path.ends_with('\n'))
path.pop_back();
if (path.starts_with("file://"))
path = path.substr(7);
if (path.empty())
return;
}
try {
const auto MTIME = std::filesystem::last_write_time(path);
if (OLDPATH == path && MTIME == modificationTime)
return;
modificationTime = MTIME;
} catch (std::exception& e) {
path = OLDPATH;
Debug::log(ERR, "{}", e.what());
return;
}
if (!pendingResourceID.empty())
return;
request.id = std::string{"image:"} + path + ",time:" + std::to_string(modificationTime.time_since_epoch().count());
pendingResourceID = request.id;
request.asset = path;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE;
request.callback = onAssetCallback;
request.callbackData = this;
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CImage::plantTimer() {
if (reloadTime == 0) {
imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true);
} else if (reloadTime > 0)
imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onTimer, this, false);
}
CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props) : CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props) :
viewport(viewport_), resourceID(resourceID_), output(output_), shadow(this, props, viewport_) { viewport(viewport_), resourceID(resourceID_), output(output_), shadow(this, props, viewport_) {
@ -14,11 +83,38 @@ CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& r
valign = std::any_cast<Hyprlang::STRING>(props.at("valign")); valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate")); angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
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"));
try {
modificationTime = std::filesystem::last_write_time(path);
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
angle = angle * M_PI / 180.0; angle = angle * M_PI / 180.0;
plantTimer();
} }
bool CImage::draw(const SRenderData& data) { bool CImage::draw(const SRenderData& data) {
if (!pendingResourceID.empty()) {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
if (newAsset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
} else if (resourceID != pendingResourceID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
imageFB.release();
asset = newAsset;
resourceID = pendingResourceID;
firstRender = true;
}
pendingResourceID = "";
}
}
if (resourceID.empty()) if (resourceID.empty())
return false; return false;
@ -54,6 +150,8 @@ bool CImage::draw(const SRenderData& data) {
borderBox.round(); borderBox.round();
imageFB.alloc(borderBox.w, borderBox.h, true); imageFB.alloc(borderBox.w, borderBox.h, true);
g_pRenderer->pushFb(imageFB.m_iFb); g_pRenderer->pushFb(imageFB.m_iFb);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
if (border > 0) if (border > 0)
g_pRenderer->renderRect(borderBox, color, ALLOWROUND ? rounding : std::min(borderBox.w, borderBox.h) / 2.0); g_pRenderer->renderRect(borderBox, color, ALLOWROUND ? rounding : std::min(borderBox.w, borderBox.h) / 2.0);
@ -84,3 +182,15 @@ bool CImage::draw(const SRenderData& data) {
return data.opacity < 1.0; return data.opacity < 1.0;
} }
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
const auto PIMAGE = (CImage*)data;
PIMAGE->renderSuper();
}
void CImage::renderSuper() {
g_pHyprlock->renderOutput(output->stringPort);
if (!pendingResourceID.empty()) /* did not consume the pending resource */
g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this);
}

View File

@ -3,8 +3,11 @@
#include "IWidget.hpp" #include "IWidget.hpp"
#include "../../helpers/Vector2D.hpp" #include "../../helpers/Vector2D.hpp"
#include "../../helpers/Color.hpp" #include "../../helpers/Color.hpp"
#include "../../core/Timer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include "Shadowable.hpp" #include "Shadowable.hpp"
#include <string> #include <string>
#include <filesystem>
#include <unordered_map> #include <unordered_map>
#include <any> #include <any>
@ -14,26 +17,38 @@ class COutput;
class CImage : public IWidget { class CImage : public IWidget {
public: public:
CImage(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props); CImage(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props);
~CImage();
virtual bool draw(const SRenderData& data); virtual bool draw(const SRenderData& data);
void renderSuper();
void onTimerUpdate();
void plantTimer();
private: private:
CFramebuffer imageFB; CFramebuffer imageFB;
int size; int size;
int rounding; int rounding;
double border; double border;
double angle; double angle;
CColor color; CColor color;
Vector2D pos; Vector2D pos;
std::string halign, valign; std::string halign, valign, path;
bool firstRender = true; bool firstRender = true;
Vector2D viewport; int reloadTime;
std::string resourceID; std::string reloadCommand;
SPreloadedAsset* asset = nullptr; std::filesystem::file_time_type modificationTime;
COutput* output = nullptr; std::shared_ptr<CTimer> imageTimer;
CShadowable shadow; CAsyncResourceGatherer::SPreloadRequest request;
Vector2D viewport;
std::string resourceID;
std::string pendingResourceID; // if reloading image
SPreloadedAsset* asset = nullptr;
COutput* output = nullptr;
CShadowable shadow;
}; };