Skip to content

Commit

Permalink
image: add reload options (#247)
Browse files Browse the repository at this point in the history
* image: add reload options

* check for actual file changes

* use modtime

* check only same paths

* add Nix HM
  • Loading branch information
bvr-yr authored Apr 7, 2024
1 parent 071ebce commit bbbb960
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 19 deletions.
14 changes: 14 additions & 0 deletions nix/hm-module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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}
Expand Down
4 changes: 4 additions & 0 deletions src/config/ConfigManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down Expand Up @@ -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"),
}
});
Expand Down
21 changes: 21 additions & 0 deletions src/renderer/AsyncResourceGatherer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/AsyncResourceGatherer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class CAsyncResourceGatherer {

void asyncAssetSpinLock();
void renderText(const SPreloadRequest& rq);
void renderImage(const SPreloadRequest& rq);

struct {
std::condition_variable loopGuard;
Expand Down Expand Up @@ -88,4 +89,4 @@ class CAsyncResourceGatherer {
std::unordered_map<std::string, SPreloadedAsset> assets;

void gather();
};
};
110 changes: 110 additions & 0 deletions src/renderer/widgets/Image.cpp
Original file line number Diff line number Diff line change
@@ -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_) {

Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
51 changes: 33 additions & 18 deletions src/renderer/widgets/Image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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);

private:
CFramebuffer imageFB;

int size;
int rounding;
double border;
double angle;
CColor color;
Vector2D pos;

std::string halign, valign;
void renderSuper();
void onTimerUpdate();
void plantTimer();

bool firstRender = true;

Vector2D viewport;
std::string resourceID;
SPreloadedAsset* asset = nullptr;
COutput* output = nullptr;
CShadowable shadow;
private:
CFramebuffer imageFB;

int size;
int rounding;
double border;
double angle;
CColor color;
Vector2D pos;

std::string halign, valign, path;

bool firstRender = true;

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;
};

0 comments on commit bbbb960

Please sign in to comment.