diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 4029df6..bb3cdb0 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -179,6 +179,95 @@ in { ]; }; + shapes = mkOption { + description = "Shape configurations"; + type = listOf (submodule { + options = { + monitor = mkOption { + description = "The monitor to draw a shape"; + type = str; + default = ""; + }; + + size = { + x = mkOption { + description = "Width of the shape"; + type = int; + default = 360; + }; + y = mkOption { + description = "Height of the shape"; + type = int; + default = 60; + }; + }; + + color = mkOption { + description = "Color of the shape"; + type = str; + default = "rgba(22, 17, 17, 1.0)"; + }; + + rounding = mkOption { + description = "Rounding of the shape"; + type = int; + default = -1; + }; + + border_size = mkOption { + description = "Size of shape border"; + type = int; + default = 4; + }; + + border_color = mkOption { + description = "Color of shape border"; + type = str; + default = "rgba(0, 207, 230, 1.0)"; + }; + + rotate = mkOption { + description = "Shape rotation angle"; + type = float; + default = 0.0; + }; + + xray = mkOption { + description = "Whether to make a transparent \"hole\" in the background"; + type = bool; + default = false; + }; + + position = { + x = mkOption { + description = "X position of the shape"; + type = int; + default = 0; + }; + y = mkOption { + description = "Y position of the shape"; + type = int; + default = 80; + }; + }; + + halign = mkOption { + description = "Horizontal alignment of the shape"; + type = str; + default = "center"; + }; + + valign = mkOption { + description = "Vertical alignment of the shape"; + type = str; + default = "center"; + }; + } + // shadow; + }); + default = []; + }; + images = mkOption { description = "Image configurations"; type = listOf (submodule { @@ -561,6 +650,24 @@ in { '') cfg.backgrounds)} + ${builtins.concatStringsSep "\n" (map (shape: '' + shape { + monitor = ${shape.monitor} + size = ${toString shape.size.x}, ${toString shape.size.y} + color = ${shape.color} + rounding = ${toString shape.rounding} + border_size = ${toString shape.border_size} + border_color = ${shape.border_color} + rotate = ${toString shape.rotate} + xray = ${boolToString shape.xray} + + position = ${toString shape.position.x}, ${toString shape.position.y} + halign = ${shape.halign} + valign = ${shape.valign} + } + '') + cfg.shapes)} + ${builtins.concatStringsSep "\n" (map (image: '' image { monitor = ${image.monitor} diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 4c8d8a8..394c003 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -62,6 +62,20 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("background", "vibrancy", Hyprlang::FLOAT{0.1686}); m_config.addSpecialConfigValue("background", "vibrancy_darkness", Hyprlang::FLOAT{0.05}); + m_config.addSpecialCategory("shape", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); + m_config.addSpecialConfigValue("shape", "monitor", Hyprlang::STRING{""}); + m_config.addSpecialConfigValue("shape", "size", Hyprlang::VEC2{100, 100}); + m_config.addSpecialConfigValue("shape", "rounding", Hyprlang::INT{0}); + m_config.addSpecialConfigValue("shape", "border_size", Hyprlang::INT{0}); + m_config.addSpecialConfigValue("shape", "border_color", Hyprlang::INT{0xFF00CFE6}); + m_config.addSpecialConfigValue("shape", "color", Hyprlang::INT{0xFF111111}); + m_config.addSpecialConfigValue("shape", "position", Hyprlang::VEC2{0, 80}); + m_config.addSpecialConfigValue("shape", "halign", Hyprlang::STRING{"center"}); + m_config.addSpecialConfigValue("shape", "valign", Hyprlang::STRING{"center"}); + m_config.addSpecialConfigValue("shape", "rotate", Hyprlang::FLOAT{0}); + m_config.addSpecialConfigValue("shape", "xray", Hyprlang::INT{0}); + SHADOWABLE("shape"); + m_config.addSpecialCategory("image", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("image", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("image", "path", Hyprlang::STRING{""}); @@ -169,6 +183,30 @@ std::vector CConfigManager::getWidgetConfigs() { // clang-format on } + // + keys = m_config.listKeysForSpecialCategory("shape"); + for (auto& k : keys) { + // clang-format off + result.push_back(CConfigManager::SWidgetConfig{ + "shape", + std::any_cast(m_config.getSpecialConfigValue("shape", "monitor", k.c_str())), + { + {"size", m_config.getSpecialConfigValue("shape", "size", k.c_str())}, + {"rounding", m_config.getSpecialConfigValue("shape", "rounding", k.c_str())}, + {"border_size", m_config.getSpecialConfigValue("shape", "border_size", k.c_str())}, + {"border_color", m_config.getSpecialConfigValue("shape", "border_color", k.c_str())}, + {"color", m_config.getSpecialConfigValue("shape", "color", k.c_str())}, + {"position", m_config.getSpecialConfigValue("shape", "position", k.c_str())}, + {"halign", m_config.getSpecialConfigValue("shape", "halign", k.c_str())}, + {"valign", m_config.getSpecialConfigValue("shape", "valign", k.c_str())}, + {"rotate", m_config.getSpecialConfigValue("shape", "rotate", k.c_str())}, + {"xray", m_config.getSpecialConfigValue("shape", "xray", k.c_str())}, + SHADOWABLE("shape"), + } + }); + // clang-format on + } + // keys = m_config.listKeysForSpecialCategory("image"); for (auto& k : keys) { diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index 53ccfc9..b1fdbf1 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -17,6 +17,7 @@ #include "widgets/Background.hpp" #include "widgets/Label.hpp" #include "widgets/Image.hpp" +#include "widgets/Shape.hpp" inline const float fullVerts[] = { 1, 0, // top right @@ -324,6 +325,8 @@ std::vector>* CRenderer::getOrCreateWidgetsFor(const CS widgets[surf].emplace_back(std::make_unique(surf->size, c.values, surf->output->stringPort)); } else if (c.type == "label") { widgets[surf].emplace_back(std::make_unique(surf->size, c.values, surf->output->stringPort)); + } else if (c.type == "shape") { + widgets[surf].emplace_back(std::make_unique(surf->size, c.values)); } else if (c.type == "image") { const std::string PATH = std::any_cast(c.values.at("path")); diff --git a/src/renderer/widgets/Image.cpp b/src/renderer/widgets/Image.cpp index 5a9c230..cf416df 100644 --- a/src/renderer/widgets/Image.cpp +++ b/src/renderer/widgets/Image.cpp @@ -132,12 +132,14 @@ bool CImage::draw(const SRenderData& data) { if (!imageFB.isAllocated()) { - const Vector2D TEXSIZE = asset->texture.m_vSize; - const float SCALEX = size / TEXSIZE.x; - const float SCALEY = size / TEXSIZE.y; + const Vector2D IMAGEPOS = {border, border}; + const Vector2D BORDERPOS = {0.0, 0.0}; + 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}; + // image with borders offset, with extra pixel for anti-aliasing when rotated + CBox texbox = {angle == 0 ? IMAGEPOS : IMAGEPOS + Vector2D{1.0, 1.0}, TEXSIZE}; texbox.w *= std::max(SCALEX, SCALEY); texbox.h *= std::max(SCALEX, SCALEY); @@ -145,16 +147,19 @@ bool CImage::draw(const SRenderData& data) { 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}}; + CBox borderBox = {angle == 0 ? BORDERPOS : BORDERPOS + Vector2D{1.0, 1.0}, texbox.size() + IMAGEPOS * 2.0}; borderBox.round(); - imageFB.alloc(borderBox.w, borderBox.h, true); + + const Vector2D FBSIZE = angle == 0 ? borderBox.size() : borderBox.size() + Vector2D{2.0, 2.0}; + + imageFB.alloc(FBSIZE.x, FBSIZE.y, 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); + g_pRenderer->renderRect(borderBox, color, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : 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); diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index bb06999..d988a6f 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -116,12 +116,13 @@ bool CLabel::draw(const SRenderData& data) { } } + shadow.draw(data); + // 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); return false; diff --git a/src/renderer/widgets/Shape.cpp b/src/renderer/widgets/Shape.cpp new file mode 100644 index 0000000..2ff7553 --- /dev/null +++ b/src/renderer/widgets/Shape.cpp @@ -0,0 +1,89 @@ +#include "Shape.hpp" +#include "../Renderer.hpp" +#include + +CShape::CShape(const Vector2D& viewport_, const std::unordered_map& props) : shadow(this, props, viewport_) { + + size = std::any_cast(props.at("size")); + rounding = std::any_cast(props.at("rounding")); + border = std::any_cast(props.at("border_size")); + color = std::any_cast(props.at("color")); + borderColor = std::any_cast(props.at("border_color")); + 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")); + xray = std::any_cast(props.at("xray")); + + viewport = viewport_; + angle = angle * M_PI / 180.0; + + const Vector2D VBORDER = {border, border}; + const Vector2D REALSIZE = size + VBORDER * 2.0; + const Vector2D OFFSET = angle == 0 ? Vector2D{0.0, 0.0} : Vector2D{1.0, 1.0}; + + pos = posFromHVAlign(viewport, xray ? size : REALSIZE + OFFSET * 2.0, pos, halign, valign, xray ? 0 : angle); + + if (xray) { + shapeBox = {pos, size}; + borderBox = {pos - VBORDER, REALSIZE}; + } else { + shapeBox = {OFFSET + VBORDER, size}; + borderBox = {OFFSET, REALSIZE}; + } +} + +bool CShape::draw(const SRenderData& data) { + + if (firstRender) { + firstRender = false; + shadow.markShadowDirty(); + } + + shadow.draw(data); + + const auto MINHALFBORDER = std::min(borderBox.w, borderBox.h) / 2.0; + + if (xray) { + if (border > 0) { + const int PIROUND = std::min(MINHALFBORDER, std::round(border * M_PI)); + CColor borderCol = borderColor; + borderCol.a *= data.opacity; + g_pRenderer->renderRect(borderBox, borderCol, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND)); + } + + glEnable(GL_SCISSOR_TEST); + glScissor(shapeBox.x, shapeBox.y, shapeBox.width, shapeBox.height); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + + return data.opacity < 1.0; + } + + if (!shapeFB.isAllocated()) { + const auto MINHALFSHAPE = std::min(shapeBox.w, shapeBox.h) / 2.0; + const bool ALLOWROUND = rounding > -1 && rounding < MINHALFSHAPE; + + shapeFB.alloc(borderBox.width + borderBox.x * 2.0, borderBox.height + borderBox.y * 2.0, true); + g_pRenderer->pushFb(shapeFB.m_iFb); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + if (border > 0) + g_pRenderer->renderRect(borderBox, borderColor, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : MINHALFBORDER); + + g_pRenderer->renderRect(shapeBox, color, ALLOWROUND ? rounding : MINHALFSHAPE); + g_pRenderer->popFb(); + } + + CTexture* tex = &shapeFB.m_cTex; + CBox texbox = {pos, tex->m_vSize}; + + texbox.round(); + 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/Shape.hpp b/src/renderer/widgets/Shape.hpp new file mode 100644 index 0000000..d749d68 --- /dev/null +++ b/src/renderer/widgets/Shape.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "IWidget.hpp" +#include "../../helpers/Vector2D.hpp" +#include "../../helpers/Color.hpp" +#include "../../helpers/Box.hpp" +#include "Shadowable.hpp" +#include +#include +#include + +class CShape : public IWidget { + public: + CShape(const Vector2D& viewport, const std::unordered_map& props); + + virtual bool draw(const SRenderData& data); + + private: + CFramebuffer shapeFB; + + int rounding; + double border; + double angle; + CColor color; + CColor borderColor; + Vector2D size; + Vector2D pos; + CBox shapeBox; + CBox borderBox; + bool xray; + + std::string halign, valign; + + bool firstRender = true; + + Vector2D viewport; + CShadowable shadow; +};