From 84c6282d6a1f6ab4ecff791e0b957d354f894121 Mon Sep 17 00:00:00 2001 From: bvr-yr <130279855+bvr-yr@users.noreply.github.com> Date: Fri, 29 Mar 2024 22:01:11 +0300 Subject: [PATCH] widgets: add `rotate` option to label and image (#234) * widgets: add `rotate` option to label and image * use framebuffer for image --- nix/hm-module.nix | 14 +++++++++ src/config/ConfigManager.cpp | 4 +++ src/helpers/Vector2D.cpp | 6 ++++ src/helpers/Vector2D.hpp | 1 + src/renderer/Renderer.cpp | 4 +-- src/renderer/widgets/IWidget.cpp | 18 +++++++---- src/renderer/widgets/IWidget.hpp | 3 +- src/renderer/widgets/Image.cpp | 54 +++++++++++++++++++++----------- src/renderer/widgets/Image.hpp | 5 ++- src/renderer/widgets/Label.cpp | 10 ++++-- src/renderer/widgets/Label.hpp | 3 +- 11 files changed, 90 insertions(+), 32 deletions(-) diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 029c281..599b12b 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -219,6 +219,12 @@ in { default = "rgb(221, 221, 221)"; }; + rotate = mkOption { + description = "Image rotation angle"; + type = float; + default = 0; + }; + position = { x = mkOption { description = "X position of the image"; @@ -471,6 +477,12 @@ in { default = "Noto Sans"; }; + rotate = mkOption { + description = "Label rotation angle"; + type = float; + default = 0; + }; + position = { x = mkOption { description = "X position of the label"; @@ -544,6 +556,7 @@ in { rounding = ${toString image.rounding} border_size = ${toString image.border_size} border_color = ${image.border_color} + rotate = ${toString image.rotate} position = ${toString image.position.x}, ${toString image.position.y} halign = ${image.halign} @@ -597,6 +610,7 @@ in { color = ${label.color} font_size = ${toString label.font_size} font_family = ${label.font_family} + rotate = ${toString label.rotate} shadow_passes = ${toString label.shadow_passes} shadow_size = ${toString label.shadow_size} shadow_color = ${label.shadow_color} diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 5fddf1c..a074d7d 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -72,6 +72,7 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("image", "position", Hyprlang::VEC2{0, 200}); m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"}); + m_config.addSpecialConfigValue("image", "rotate", Hyprlang::FLOAT{0}); SHADOWABLE("image"); m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); @@ -113,6 +114,7 @@ void CConfigManager::init() { 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.addSpecialConfigValue("label", "rotate", Hyprlang::FLOAT{0}); SHADOWABLE("label"); m_config.registerHandler(&::handleSource, "source", {false}); @@ -181,6 +183,7 @@ std::vector CConfigManager::getWidgetConfigs() { {"position", m_config.getSpecialConfigValue("image", "position", k.c_str())}, {"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())}, SHADOWABLE("image"), } }); @@ -241,6 +244,7 @@ std::vector CConfigManager::getWidgetConfigs() { {"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())}, + {"rotate", m_config.getSpecialConfigValue("label", "rotate", k.c_str())}, SHADOWABLE("label"), } }); diff --git a/src/helpers/Vector2D.cpp b/src/helpers/Vector2D.cpp index 4cd3476..b299cbc 100644 --- a/src/helpers/Vector2D.cpp +++ b/src/helpers/Vector2D.cpp @@ -49,3 +49,9 @@ double Vector2D::size() const { Vector2D Vector2D::getComponentMax(const Vector2D& other) const { return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y)); } + +Vector2D Vector2D::rotated(const double& ang) const { + const double COS = std::abs(std::cos(ang)); + const double SIN = std::abs(std::sin(ang)); + return Vector2D(x * COS + y * SIN, x * SIN + y * COS); +} diff --git a/src/helpers/Vector2D.hpp b/src/helpers/Vector2D.hpp index cf551fb..188d4b9 100644 --- a/src/helpers/Vector2D.hpp +++ b/src/helpers/Vector2D.hpp @@ -98,6 +98,7 @@ class Vector2D { Vector2D round() const; Vector2D getComponentMax(const Vector2D& other) const; + Vector2D rotated(const double& ang) const; }; /** diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index e568bc6..53ccfc9 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -226,7 +226,7 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) { float matrix[9]; - wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, box.rot, projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; @@ -258,7 +258,7 @@ void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) { void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, std::optional tr) { float matrix[9]; - wlr_matrix_project_box(matrix, &box, tr.value_or(WL_OUTPUT_TRANSFORM_FLIPPED_180) /* ugh coordinate spaces */, 0, + wlr_matrix_project_box(matrix, &box, tr.value_or(WL_OUTPUT_TRANSFORM_FLIPPED_180) /* ugh coordinate spaces */, box.rot, projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; diff --git a/src/renderer/widgets/IWidget.cpp b/src/renderer/widgets/IWidget.cpp index 07bdea1..5d52b7a 100644 --- a/src/renderer/widgets/IWidget.cpp +++ b/src/renderer/widgets/IWidget.cpp @@ -16,25 +16,31 @@ namespace std { } #endif -Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign) { +Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, const double& ang) { + + // offset after rotation for alignment + Vector2D rot; + if (ang != 0) + rot = (size - size.rotated(ang)) / 2.0; + Vector2D pos = offset; if (halign == "center") pos.x += viewport.x / 2.0 - size.x / 2.0; else if (halign == "left") - pos.x += 0; + pos.x += 0 - rot.x; else if (halign == "right") - pos.x += viewport.x - size.x; + pos.x += viewport.x - size.x + rot.x; else if (halign != "none") Debug::log(ERR, "IWidget: invalid halign {}", halign); if (valign == "center") pos.y += viewport.y / 2.0 - size.y / 2.0; else if (valign == "top") - pos.y += viewport.y - size.y; + pos.y += viewport.y - size.y + rot.y; else if (valign == "bottom") - pos.y += size.y; + pos.y += 0 - rot.y; else if (valign != "none") - Debug::log(ERR, "IWidget: invalid halign {}", halign); + Debug::log(ERR, "IWidget: invalid valign {}", valign); return pos; } diff --git a/src/renderer/widgets/IWidget.hpp b/src/renderer/widgets/IWidget.hpp index 8886ab2..ca1a519 100644 --- a/src/renderer/widgets/IWidget.hpp +++ b/src/renderer/widgets/IWidget.hpp @@ -11,7 +11,8 @@ class IWidget { virtual bool draw(const SRenderData& data) = 0; - virtual Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign); + virtual Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, + const double& ang = 0); struct SFormatResult { std::string formatted; diff --git a/src/renderer/widgets/Image.cpp b/src/renderer/widgets/Image.cpp index 97c81ff..e513461 100644 --- a/src/renderer/widgets/Image.cpp +++ b/src/renderer/widgets/Image.cpp @@ -12,6 +12,9 @@ CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& r pos = std::any_cast(props.at("position")); halign = std::any_cast(props.at("halign")); valign = std::any_cast(props.at("valign")); + angle = std::any_cast(props.at("rotate")); + + angle = angle * M_PI / 180.0; } bool CImage::draw(const SRenderData& data) { @@ -31,7 +34,36 @@ bool CImage::draw(const SRenderData& data) { return false; } - CTexture* tex = &asset->texture; + if (!imageFB.isAllocated()) { + + const Vector2D TEXSIZE = asset->texture.m_vSize; + const float SCALEX = size / TEXSIZE.x; + const float SCALEY = size / TEXSIZE.y; + + // image with borders offset + CBox texbox = {{border, border}, TEXSIZE}; + + texbox.w *= std::max(SCALEX, SCALEY); + texbox.h *= std::max(SCALEX, SCALEY); + + const bool ALLOWROUND = rounding > -1 && rounding < std::min(texbox.w, texbox.h) / 2.0; + + // plus borders if any + CBox borderBox = {{}, {texbox.w + border * 2.0, texbox.h + border * 2.0}}; + + borderBox.round(); + imageFB.alloc(borderBox.w, borderBox.h, true); + g_pRenderer->pushFb(imageFB.m_iFb); + + if (border > 0) + g_pRenderer->renderRect(borderBox, color, ALLOWROUND ? rounding : std::min(borderBox.w, borderBox.h) / 2.0); + + texbox.round(); + g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ALLOWROUND ? rounding : std::min(texbox.w, texbox.h) / 2.0, WL_OUTPUT_TRANSFORM_NORMAL); + g_pRenderer->popFb(); + } + + CTexture* tex = &imageFB.m_cTex; CBox texbox = {{}, tex->m_vSize}; if (firstRender) { @@ -39,30 +71,16 @@ bool CImage::draw(const SRenderData& data) { shadow.markShadowDirty(); } - const float SCALEX = size / tex->m_vSize.x; - const float SCALEY = size / tex->m_vSize.y; - - texbox.w *= std::max(SCALEX, SCALEY); - texbox.h *= std::max(SCALEX, SCALEY); - shadow.draw(data); - const bool ALLOWROUND = rounding > -1 && rounding < std::min(texbox.w, texbox.h) / 2.0; - const auto TEXPOS = posFromHVAlign(viewport, Vector2D{texbox.w, texbox.h}, pos, halign, valign); + const auto TEXPOS = posFromHVAlign(viewport, tex->m_vSize, pos, halign, valign, angle); texbox.x = TEXPOS.x; texbox.y = TEXPOS.y; - if (border > 0) { - CBox borderBox = {TEXPOS - Vector2D{(double)border, (double)border}, texbox.size() + Vector2D{(double)border * 2.0, (double)border * 2.0}}; - CColor borderCol = color; - borderCol.a *= data.opacity; - borderBox.round(); - g_pRenderer->renderRect(borderBox, borderCol, ALLOWROUND ? rounding : std::min(borderBox.w, borderBox.h) / 2.0); - } - texbox.round(); - g_pRenderer->renderTexture(texbox, *tex, data.opacity, ALLOWROUND ? rounding : std::min(texbox.w, texbox.h) / 2.0, WL_OUTPUT_TRANSFORM_FLIPPED_180); + texbox.rot = angle; + g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180); return data.opacity < 1.0; } diff --git a/src/renderer/widgets/Image.hpp b/src/renderer/widgets/Image.hpp index d473a1e..1747378 100644 --- a/src/renderer/widgets/Image.hpp +++ b/src/renderer/widgets/Image.hpp @@ -18,9 +18,12 @@ class CImage : public IWidget { virtual bool draw(const SRenderData& data); private: + CFramebuffer imageFB; + int size; int rounding; - int border; + double border; + double angle; CColor color; Vector2D pos; diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index 8813eb3..bb06999 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -87,6 +87,9 @@ CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map(props.at("halign")); valign = std::any_cast(props.at("valign")); + angle = std::any_cast(props.at("rotate")); + angle = angle * M_PI / 180.0; + plantTimer(); } @@ -97,8 +100,6 @@ bool CLabel::draw(const SRenderData& data) { if (!asset) return true; - // calc pos - pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign); shadow.markShadowDirty(); } @@ -111,12 +112,15 @@ bool CLabel::draw(const SRenderData& data) { asset = newAsset; resourceID = pendingResourceID; pendingResourceID = ""; - pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign); shadow.markShadowDirty(); } } + // calc pos + pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign, angle); + CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y}; + box.rot = angle; shadow.draw(data); g_pRenderer->renderTexture(box, asset->texture, data.opacity); diff --git a/src/renderer/widgets/Label.hpp b/src/renderer/widgets/Label.hpp index 5fb6bf0..eb6a775 100644 --- a/src/renderer/widgets/Label.hpp +++ b/src/renderer/widgets/Label.hpp @@ -32,6 +32,7 @@ class CLabel : public IWidget { Vector2D viewport; Vector2D pos; Vector2D configPos; + double angle; std::string resourceID; std::string pendingResourceID; // if dynamic label std::string halign, valign; @@ -44,4 +45,4 @@ class CLabel : public IWidget { std::shared_ptr labelTimer; CShadowable shadow; -}; \ No newline at end of file +};