Hyprland/src/render/decorations/CHyprGroupBarDecoration.cpp

313 lines
12 KiB
C++
Raw Normal View History

#include "CHyprGroupBarDecoration.hpp"
#include "../../Compositor.hpp"
#include <ranges>
#include <pango/pangocairo.h>
// shared things to conserve VRAM
static std::deque<std::unique_ptr<CTitleTex>> m_dpTitleTextures;
static CTexture m_tGradientActive;
static CTexture m_tGradientInactive;
CHyprGroupBarDecoration::CHyprGroupBarDecoration(CWindow* pWindow) {
m_pWindow = pWindow;
}
CHyprGroupBarDecoration::~CHyprGroupBarDecoration() {}
SWindowDecorationExtents CHyprGroupBarDecoration::getWindowDecorationExtents() {
return m_seExtents;
}
eDecorationType CHyprGroupBarDecoration::getDecorationType() {
return DECORATION_GROUPBAR;
}
2023-05-22 20:52:41 +02:00
constexpr int BAR_INDICATOR_HEIGHT = 3;
constexpr int BAR_PADDING_OUTER_VERT = 2;
constexpr int BAR_TEXT_PAD = 2;
2023-05-22 20:52:41 +02:00
//
void CHyprGroupBarDecoration::updateWindow(CWindow* pWindow) {
damageEntire();
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID);
2022-06-27 00:25:37 +02:00
const auto WORKSPACEOFFSET = PWORKSPACE && !pWindow->m_bPinned ? PWORKSPACE->m_vRenderOffset.vec() : Vector2D();
static auto* const PRENDERTITLES = &g_pConfigManager->getConfigValuePtr("misc:render_titles_in_groupbar")->intValue;
static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("misc:groupbar_titles_font_size")->intValue;
if (pWindow->m_vRealPosition.vec() + WORKSPACEOFFSET != m_vLastWindowPos || pWindow->m_vRealSize.vec() != m_vLastWindowSize) {
// we draw 3px above the window's border with 3px
2023-05-22 21:43:37 +02:00
static auto* const PBORDERSIZE = &g_pConfigManager->getConfigValuePtr("general:border_size")->intValue;
m_seExtents.topLeft = Vector2D(0, *PBORDERSIZE + BAR_PADDING_OUTER_VERT * 2 + BAR_INDICATOR_HEIGHT + (*PRENDERTITLES ? *PTITLEFONTSIZE : 0));
m_seExtents.bottomRight = Vector2D();
m_vLastWindowPos = pWindow->m_vRealPosition.vec() + WORKSPACEOFFSET;
m_vLastWindowSize = pWindow->m_vRealSize.vec();
invalidateTextures();
}
2023-02-19 22:07:32 +01:00
if (!m_pWindow->m_sGroupData.pNextWindow) {
m_pWindow->m_vDecosToRemove.push_back(this);
return;
}
2023-02-19 22:07:32 +01:00
m_dwGroupMembers.clear();
CWindow* curr = pWindow;
CWindow* head = nullptr;
while (!curr->m_sGroupData.head) {
curr = curr->m_sGroupData.pNextWindow;
}
2023-02-19 22:07:32 +01:00
head = curr;
m_dwGroupMembers.push_back(curr);
curr = curr->m_sGroupData.pNextWindow;
while (curr != head) {
m_dwGroupMembers.push_back(curr);
curr = curr->m_sGroupData.pNextWindow;
}
damageEntire();
if (m_dwGroupMembers.size() == 0) {
m_pWindow->m_vDecosToRemove.push_back(this);
return;
}
}
void CHyprGroupBarDecoration::damageEntire() {
wlr_box dm = {m_vLastWindowPos.x - m_seExtents.topLeft.x, m_vLastWindowPos.y - m_seExtents.topLeft.y, m_vLastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x,
m_seExtents.topLeft.y};
g_pHyprRenderer->damageBox(&dm);
}
void CHyprGroupBarDecoration::draw(CMonitor* pMonitor, float a, const Vector2D& offset) {
// get how many bars we will draw
int barsToDraw = m_dwGroupMembers.size();
static auto* const PRENDERTITLES = &g_pConfigManager->getConfigValuePtr("misc:render_titles_in_groupbar")->intValue;
static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("misc:groupbar_titles_font_size")->intValue;
2023-05-22 21:43:37 +02:00
static auto* const PBORDERSIZE = &g_pConfigManager->getConfigValuePtr("general:border_size")->intValue;
2023-05-22 22:07:32 +02:00
static auto* const PGRADIENTS = &g_pConfigManager->getConfigValuePtr("misc:groupbar_gradients")->intValue;
if (barsToDraw < 1 || m_pWindow->isHidden() || !g_pCompositor->windowValidMapped(m_pWindow))
return;
2022-09-23 17:47:58 +02:00
if (!m_pWindow->m_sSpecialRenderData.decorate)
return;
const int PAD = 2; //2px
const int BARW = (m_vLastWindowSize.x - PAD * (barsToDraw - 1)) / barsToDraw;
int xoff = 0;
for (int i = 0; i < barsToDraw; ++i) {
2023-05-22 20:52:41 +02:00
wlr_box rect = {m_vLastWindowPos.x + xoff - pMonitor->vecPosition.x + offset.x,
2023-05-22 21:43:37 +02:00
m_vLastWindowPos.y - BAR_PADDING_OUTER_VERT - *PBORDERSIZE - BAR_INDICATOR_HEIGHT - pMonitor->vecPosition.y + offset.y, BARW, BAR_INDICATOR_HEIGHT};
if (rect.width <= 0 || rect.height <= 0)
break;
2022-09-18 13:13:16 +02:00
scaleBox(&rect, pMonitor->scale);
2023-02-19 22:07:32 +01:00
static auto* const PGROUPCOLACTIVE = &g_pConfigManager->getConfigValuePtr("general:col.group_border_active")->data;
static auto* const PGROUPCOLINACTIVE = &g_pConfigManager->getConfigValuePtr("general:col.group_border")->data;
2022-09-03 22:49:45 +02:00
2022-12-31 17:04:41 +01:00
CColor color = m_dwGroupMembers[i] == g_pCompositor->m_pLastWindow ? ((CGradientValueData*)PGROUPCOLACTIVE->get())->m_vColors[0] :
((CGradientValueData*)PGROUPCOLINACTIVE->get())->m_vColors[0];
2022-06-25 20:31:54 +02:00
color.a *= a;
g_pHyprOpenGL->renderRect(&rect, color);
// render title if necessary
if (*PRENDERTITLES) {
CTitleTex* pTitleTex = textureFromTitle(m_dwGroupMembers[i]->m_szTitle);
if (!pTitleTex)
pTitleTex =
m_dpTitleTextures
.emplace_back(std::make_unique<CTitleTex>(m_dwGroupMembers[i], Vector2D{BARW * pMonitor->scale, (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * pMonitor->scale}))
.get();
rect.height = (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * 0.8 * pMonitor->scale;
rect.y -= rect.height;
rect.width = BARW * pMonitor->scale;
refreshGradients();
2023-05-22 22:07:32 +02:00
if (*PGRADIENTS)
g_pHyprOpenGL->renderTexture((m_dwGroupMembers[i] == g_pCompositor->m_pLastWindow ? m_tGradientActive : m_tGradientInactive), &rect, 1.0);
rect.y -= (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * 0.2 * pMonitor->scale;
rect.height = (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * pMonitor->scale;
g_pHyprOpenGL->renderTexture(pTitleTex->tex, &rect, 1.f);
}
xoff += PAD + BARW;
}
if (*PRENDERTITLES)
clearUnusedTextures();
2023-05-22 20:52:41 +02:00
}
SWindowDecorationExtents CHyprGroupBarDecoration::getWindowDecorationReservedArea() {
2023-05-23 14:18:26 +02:00
static auto* const PRENDERTITLES = &g_pConfigManager->getConfigValuePtr("misc:render_titles_in_groupbar")->intValue;
static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("misc:groupbar_titles_font_size")->intValue;
return SWindowDecorationExtents{{0, BAR_INDICATOR_HEIGHT + BAR_PADDING_OUTER_VERT * 2 + (*PRENDERTITLES ? *PTITLEFONTSIZE : 0)}, {}};
}
CTitleTex* CHyprGroupBarDecoration::textureFromTitle(const std::string& title) {
for (auto& tex : m_dpTitleTextures) {
if (tex->szContent == title)
return tex.get();
}
return nullptr;
}
void CHyprGroupBarDecoration::clearUnusedTextures() {
for (auto& tex : m_dpTitleTextures | std::views::reverse) {
bool found = false;
for (auto& w : g_pCompositor->m_vWindows) {
if (!w->m_sGroupData.pNextWindow)
continue;
if (tex->szContent == w->m_szTitle && tex->pWindowOwner == w.get()) {
found = true;
break;
}
}
if (!found)
std::erase(m_dpTitleTextures, tex);
}
}
void CHyprGroupBarDecoration::invalidateTextures() {
m_dpTitleTextures.clear();
}
CTitleTex::CTitleTex(CWindow* pWindow, const Vector2D& bufferSize) {
szContent = pWindow->m_szTitle;
pWindowOwner = pWindow;
const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y);
const auto CAIRO = cairo_create(CAIROSURFACE);
static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("misc:groupbar_titles_font_size")->intValue;
// clear the pixmap
cairo_save(CAIRO);
cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(CAIRO);
cairo_restore(CAIRO);
// draw title using Pango
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
pango_layout_set_text(layout, szContent.c_str(), -1);
PangoFontDescription* fontDesc = pango_font_description_from_string("Sans");
pango_font_description_set_size(fontDesc, *PTITLEFONTSIZE * PANGO_SCALE);
pango_layout_set_font_description(layout, fontDesc);
pango_font_description_free(fontDesc);
const int maxWidth = bufferSize.x;
pango_layout_set_width(layout, maxWidth * PANGO_SCALE);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
cairo_set_source_rgba(CAIRO, 1.f, 1.f, 1.f, 1.f);
int layoutWidth, layoutHeight;
pango_layout_get_size(layout, &layoutWidth, &layoutHeight);
const int xOffset = std::round((bufferSize.x / 2.0 - layoutWidth / PANGO_SCALE / 2.0));
const int yOffset = std::round((bufferSize.y / 2.0 - layoutHeight / PANGO_SCALE / 2.0));
cairo_move_to(CAIRO, xOffset, yOffset);
pango_cairo_show_layout(CAIRO, layout);
g_object_unref(layout);
cairo_surface_flush(CAIROSURFACE);
// copy the data to an OpenGL texture we have
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
tex.allocate();
glBindTexture(GL_TEXTURE_2D, tex.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
#ifndef GLES2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
#endif
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
// delete cairo
cairo_destroy(CAIRO);
cairo_surface_destroy(CAIROSURFACE);
}
CTitleTex::~CTitleTex() {
tex.destroyTexture();
}
void renderGradientTo(CTexture& tex, const CColor& grad) {
const Vector2D& bufferSize = g_pCompositor->m_pLastMonitor->vecPixelSize;
const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y);
const auto CAIRO = cairo_create(CAIROSURFACE);
// clear the pixmap
cairo_save(CAIRO);
cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(CAIRO);
cairo_restore(CAIRO);
cairo_pattern_t* pattern;
pattern = cairo_pattern_create_linear(0, 0, 0, bufferSize.y);
cairo_pattern_add_color_stop_rgba(pattern, 1, grad.r, grad.g, grad.b, grad.a);
cairo_pattern_add_color_stop_rgba(pattern, 0, grad.r, grad.g, grad.b, 0);
cairo_rectangle(CAIRO, 0, 0, bufferSize.x, bufferSize.y);
cairo_set_source(CAIRO, pattern);
cairo_fill(CAIRO);
cairo_pattern_destroy(pattern);
cairo_surface_flush(CAIROSURFACE);
// copy the data to an OpenGL texture we have
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
tex.allocate();
glBindTexture(GL_TEXTURE_2D, tex.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
#ifndef GLES2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
#endif
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
// delete cairo
cairo_destroy(CAIRO);
cairo_surface_destroy(CAIROSURFACE);
}
void CHyprGroupBarDecoration::refreshGradients() {
if (m_tGradientActive.m_iTexID > 0)
return;
static auto* const PGROUPCOLACTIVE = &g_pConfigManager->getConfigValuePtr("general:col.group_border_active")->data;
static auto* const PGROUPCOLINACTIVE = &g_pConfigManager->getConfigValuePtr("general:col.group_border")->data;
renderGradientTo(m_tGradientActive, ((CGradientValueData*)PGROUPCOLACTIVE->get())->m_vColors[0]);
renderGradientTo(m_tGradientInactive, ((CGradientValueData*)PGROUPCOLINACTIVE->get())->m_vColors[0]);
}