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;
};
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 = {
x = mkOption {
description = "X position of the image";
@ -558,6 +570,8 @@ in {
border_size = ${toString image.border_size}
border_color = ${image.border_color}
rotate = ${toString image.rotate}
reload_time = ${toString image.reload_time}
reload_cmd = ${image.reload_cmd}
position = ${toString image.position.x}, ${toString image.position.y}
halign = ${image.halign}

View file

@ -73,6 +73,8 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"});
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");
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())},
{"valign", m_config.getSpecialConfigValue("image", "valign", 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"),
}
});

View file

@ -203,6 +203,25 @@ void CAsyncResourceGatherer::apply() {
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) {
SPreloadTarget target;
target.type = TARGET_IMAGE; /* text is just an image lol */
@ -314,6 +333,8 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() {
for (auto& r : requests) {
if (r.type == TARGET_TEXT) {
renderText(r);
} else if (r.type == TARGET_IMAGE) {
renderImage(r);
} else {
Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type);
continue;

View file

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

View file

@ -1,7 +1,76 @@
#include "Image.hpp"
#include "../Renderer.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp"
#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) :
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"));
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;
plantTimer();
}
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())
return false;
@ -54,6 +150,8 @@ bool CImage::draw(const SRenderData& data) {
borderBox.round();
imageFB.alloc(borderBox.w, borderBox.h, true);
g_pRenderer->pushFb(imageFB.m_iFb);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
if (border > 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;
}
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 "../../helpers/Vector2D.hpp"
#include "../../helpers/Color.hpp"
#include "../../core/Timer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include "Shadowable.hpp"
#include <string>
#include <filesystem>
#include <unordered_map>
#include <any>
@ -14,26 +17,38 @@ class COutput;
class CImage : public IWidget {
public:
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);
void renderSuper();
void onTimerUpdate();
void plantTimer();
private:
CFramebuffer imageFB;
CFramebuffer imageFB;
int size;
int rounding;
double border;
double angle;
CColor color;
Vector2D pos;
int size;
int rounding;
double border;
double angle;
CColor color;
Vector2D pos;
std::string halign, valign;
std::string halign, valign, path;
bool firstRender = true;
bool firstRender = true;
Vector2D viewport;
std::string resourceID;
SPreloadedAsset* asset = nullptr;
COutput* output = nullptr;
CShadowable shadow;
int reloadTime;
std::string reloadCommand;
std::filesystem::file_time_type modificationTime;
std::shared_ptr<CTimer> imageTimer;
CAsyncResourceGatherer::SPreloadRequest request;
Vector2D viewport;
std::string resourceID;
std::string pendingResourceID; // if reloading image
SPreloadedAsset* asset = nullptr;
COutput* output = nullptr;
CShadowable shadow;
};