This commit is contained in:
João P. Santos 2026-04-27 00:48:53 +02:00 committed by GitHub
commit cf74c22d6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 224 additions and 7 deletions

View file

@ -13,6 +13,7 @@ plugin {
gap_size = 5
bg_col = rgb(111111)
workspace_method = center current # [center/first] [workspace] e.g. first 1 or center m+1
show_label = true
gesture_distance = 300 # how far is the "max" for the gesture
}
@ -28,6 +29,9 @@ gap_size | number | gap between desktops | `5`
bg_col | color | color in gaps (between desktops) | `rgb(000000)`
workspace_method | [center/first] [workspace] | position of the desktops | `center current`
skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false`
show_label | boolean | whether to show workspace name labels | `false`
label_font_size | number | font size for workspace labels (minimum 8) | `24`
label_anchor | string | label position: `tl`, `tr`, `bl`, `br`, `tc`, `bc`, `cl`, `cr`, `cc` (top-left, top-right, bottom-left, bottom-right, top-center, bottom-center, center-left, center-right, center) | `tl`
gesture_distance | number | how far is the max for the gesture | `300`
### Keywords

View file

@ -250,6 +250,9 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty", Hyprlang::INT{0});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:show_label", Hyprlang::INT{0});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_font_size", Hyprlang::INT{24});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_anchor", Hyprlang::STRING{"tl"});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200});

View file

@ -14,6 +14,7 @@
#include <hyprland/src/helpers/time/Time.hpp>
#undef private
#include "OverviewPassElement.hpp"
#include <pango/pangocairo.h>
static void damageMonitor(WP<Hyprutils::Animation::CBaseAnimatedVariable> thisptr) {
g_pOverview->damage();
@ -30,15 +31,22 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn
const auto PMONITOR = Desktop::focusState()->monitor();
pMonitor = PMONITOR;
static auto* const* PCOLUMNS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:columns")->getDataStaticPtr();
static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr();
static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr();
static auto* const* PSKIP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty")->getDataStaticPtr();
static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr();
static auto* const* PCOLUMNS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:columns")->getDataStaticPtr();
static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr();
static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr();
static auto* const* PSKIP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty")->getDataStaticPtr();
static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr();
static auto* const* PSHOWLABEL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:show_label")->getDataStaticPtr();
static auto* const* PFONTSIZE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_font_size")->getDataStaticPtr();
static auto const* PANCHOR = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_anchor")->getDataStaticPtr();
SIDE_LENGTH = **PCOLUMNS;
GAP_WIDTH = **PGAPS;
BG_COLOR = **PCOL;
show_label = **PSHOWLABEL;
fontSize = std::max(8, (int)**PFONTSIZE);
labelAnchor = parseLabelAnchor(*PANCHOR);
// process the method
bool methodCenter = true;
@ -142,6 +150,7 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn
for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) {
COverview::SWorkspaceImage& image = images[i];
image.fb.alloc(monbox.w, monbox.h, PMONITOR->m_output->state->state().drmFormat);
image.textTex = makeShared<CTexture>();
CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX};
g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &image.fb);
@ -430,6 +439,140 @@ void COverview::render() {
g_pHyprRenderer->m_renderPass.add(makeUnique<COverviewPassElement>());
}
COverview::LabelAnchor COverview::parseLabelAnchor(const std::string& anchorStr) {
if (anchorStr == "tl") {
return LabelAnchor::TOP_LEFT;
} else if (anchorStr == "tr") {
return LabelAnchor::TOP_RIGHT;
} else if (anchorStr == "bl") {
return LabelAnchor::BOTTOM_LEFT;
} else if (anchorStr == "br") {
return LabelAnchor::BOTTOM_RIGHT;
} else if (anchorStr == "tc") {
return LabelAnchor::TOP_CENTER;
} else if (anchorStr == "bc") {
return LabelAnchor::BOTTOM_CENTER;
} else if (anchorStr == "cl") {
return LabelAnchor::CENTER_LEFT;
} else if (anchorStr == "cr") {
return LabelAnchor::CENTER_RIGHT;
} else if (anchorStr == "cc") {
return LabelAnchor::CENTER;
} else {
return LabelAnchor::TOP_LEFT; // default fallback
}
}
Vector2D COverview::calculateTextPosition(const CBox& texbox, const Vector2D& textBufferSize, const double padding, LabelAnchor anchor) {
switch (anchor) {
case LabelAnchor::TOP_LEFT:
return Vector2D{texbox.x + padding, texbox.y + padding};
case LabelAnchor::TOP_RIGHT:
return Vector2D{texbox.x + texbox.width - textBufferSize.x - padding, texbox.y + padding};
case LabelAnchor::BOTTOM_LEFT:
return Vector2D{texbox.x + padding, texbox.y + texbox.height - textBufferSize.y - padding};
case LabelAnchor::BOTTOM_RIGHT:
return Vector2D{texbox.x + texbox.width - textBufferSize.x - padding, texbox.y + texbox.height - textBufferSize.y - padding};
case LabelAnchor::TOP_CENTER:
return Vector2D{texbox.x + texbox.width / 2.0 - textBufferSize.x / 2.0, texbox.y + padding};
case LabelAnchor::BOTTOM_CENTER:
return Vector2D{texbox.x + texbox.width / 2.0 - textBufferSize.x / 2.0, texbox.y + texbox.height - textBufferSize.y - padding};
case LabelAnchor::CENTER_LEFT:
return Vector2D{texbox.x + padding, texbox.y + texbox.height / 2.0 - textBufferSize.y / 2.0};
case LabelAnchor::CENTER_RIGHT:
return Vector2D{texbox.x + texbox.width - textBufferSize.x - padding, texbox.y + texbox.height / 2.0 - textBufferSize.y / 2.0};
case LabelAnchor::CENTER:
return Vector2D{texbox.x + texbox.width / 2.0 - textBufferSize.x / 2.0, texbox.y + texbox.height / 2.0 - textBufferSize.y / 2.0};
default:
return Vector2D{texbox.x + padding, texbox.y + padding}; // fallback to TOP_LEFT
}
}
double COverview::calculateTextWidth(const std::string& text, const float scale, const int fontSize) {
// Create a temporary surface for measurement and Pango layout
const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
const auto CAIRO = cairo_create(CAIROSURFACE);
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
pango_layout_set_text(layout, text.c_str(), -1);
PangoFontDescription* fontDesc = pango_font_description_from_string("sans");
pango_font_description_set_size(fontDesc, fontSize * scale * PANGO_SCALE);
pango_font_description_set_weight(fontDesc, PANGO_WEIGHT_BOLD);
pango_layout_set_font_description(layout, fontDesc);
PangoRectangle ink_rect, logical_rect;
pango_layout_get_extents(layout, &ink_rect, &logical_rect);
// Clean up
pango_font_description_free(fontDesc);
g_object_unref(layout);
cairo_destroy(CAIRO);
cairo_surface_destroy(CAIROSURFACE);
return (double)logical_rect.width / PANGO_SCALE;
}
void COverview::renderText(SP<CTexture> out, const std::string& text, const CHyprColor& color, const Vector2D& bufferSize, const float scale, const int fontSize) {
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);
// draw text using Pango
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
pango_layout_set_text(layout, text.c_str(), -1);
PangoFontDescription* fontDesc = pango_font_description_from_string("sans");
pango_font_description_set_size(fontDesc, fontSize * scale * PANGO_SCALE);
pango_font_description_set_weight(fontDesc, PANGO_WEIGHT_BOLD);
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_NONE);
cairo_set_source_rgba(CAIRO, color.r, color.g, color.b, color.a);
PangoRectangle ink_rect, logical_rect;
pango_layout_get_extents(layout, &ink_rect, &logical_rect);
const int layoutWidth = ink_rect.width;
const int layoutHeight = logical_rect.height;
const double xOffset = (bufferSize.x / 2.0 - layoutWidth / PANGO_SCALE / 2.0);
const double yOffset = (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);
out->allocate();
glBindTexture(GL_TEXTURE_2D, out->m_texID);
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 COverview::fullRender() {
const auto GAPSIZE = (closing ? (1.0 - size->getPercent()) : size->getPercent()) * GAP_WIDTH;
@ -447,11 +590,55 @@ void COverview::fullRender() {
for (size_t y = 0; y < (size_t)SIDE_LENGTH; ++y) {
for (size_t x = 0; x < (size_t)SIDE_LENGTH; ++x) {
CBox texbox = {x * tileRenderSize.x + x * GAPSIZE, y * tileRenderSize.y + y * GAPSIZE, tileRenderSize.x, tileRenderSize.y};
const auto idx = x + y * SIDE_LENGTH;
CBox texbox = {x * tileRenderSize.x + x * GAPSIZE, y * tileRenderSize.y + y * GAPSIZE, tileRenderSize.x, tileRenderSize.y};
texbox.scale(pMonitor->m_scale).translate(pos->value());
texbox.round();
CRegion damage{0, 0, INT16_MAX, INT16_MAX};
g_pHyprOpenGL->renderTextureInternal(images[x + y * SIDE_LENGTH].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0});
g_pHyprOpenGL->renderTextureInternal(images[idx].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0});
// Render workspace name in top left corner (if enabled)
const auto& image = images[idx];
if (image.workspaceID != WORKSPACE_INVALID && image.pWorkspace && show_label) {
// Generate text for workspace name and remove "name:" prefix if present
std::string workspaceName = image.pWorkspace->getConfigName();
if (workspaceName.starts_with("name:")) {
workspaceName = workspaceName.substr(5); // Remove "name:" prefix
}
// Trim workspace name to max 30 characters
if (workspaceName.length() > 30) {
workspaceName = workspaceName.substr(0, 27) + "...";
}
// Text rendering parameters using configurable font size
const CHyprColor textColor = CHyprColor{1.0, 1.0, 1.0, 1.0}; // Solid white text
// Calculate accurate text dimensions using Pango
const double textWidthRaw = calculateTextWidth(workspaceName, pMonitor->m_scale, fontSize);
// Add padding and limit to max 70% of tile width
const double textWidth = std::min(tileRenderSize.x * 0.7, textWidthRaw + 20.0);
// Height scales with font size (1.5x font size + padding)
const double textHeight = fontSize * 1.5 + 10.0;
const Vector2D textBufferSize = Vector2D{textWidth, textHeight};
// Calculate text position based on anchor
const double padding = 10.0;
Vector2D textPos = calculateTextPosition(texbox, textBufferSize, padding, labelAnchor);
if (image.textTex->m_texID == 0) {
renderText(image.textTex, workspaceName, textColor, textBufferSize, pMonitor->m_scale, fontSize);
}
// Render semi-transparent dark background behind text
const CHyprColor bgColor = CHyprColor{0.0, 0.0, 0.0, 0.7};
CBox bgBox = {textPos.x - 5.0, textPos.y - 3.0, textBufferSize.x + 10.0, textBufferSize.y + 6.0};
g_pHyprOpenGL->renderRect(bgBox, bgColor, {.round = 0});
// Render the text
CBox textBox = {textPos.x, textPos.y, textBufferSize.x, textBufferSize.y};
g_pHyprOpenGL->renderTexture(image.textTex, textBox, {.a = 1.0});
}
}
}
}

View file

@ -5,9 +5,11 @@
#include "globals.hpp"
#include <hyprland/src/desktop/DesktopTypes.hpp>
#include <hyprland/src/render/Framebuffer.hpp>
#include <hyprland/src/render/Texture.hpp>
#include <hyprland/src/helpers/AnimatedVariable.hpp>
#include <hyprland/src/event/EventBus.hpp>
#include <vector>
#include <pango/pangocairo.h>
// saves on resources, but is a bit broken rn with blur.
// hyprland's fault, but cba to fix.
@ -17,6 +19,18 @@ class CMonitor;
class COverview {
public:
enum class LabelAnchor {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_LEFT,
BOTTOM_RIGHT,
TOP_CENTER,
BOTTOM_CENTER,
CENTER_LEFT,
CENTER_RIGHT,
CENTER
};
COverview(PHLWORKSPACE startedOn_, bool swipe = false);
~COverview();
@ -46,10 +60,18 @@ class COverview {
void redrawAll(bool forcelowres = false);
void onWorkspaceChange();
void fullRender();
void renderText(SP<CTexture> out, const std::string& text, const CHyprColor& color, const Vector2D& bufferSize, const float scale, const int fontSize);
double calculateTextWidth(const std::string& text, const float scale, const int fontSize);
LabelAnchor parseLabelAnchor(const std::string& anchorStr);
Vector2D calculateTextPosition(const CBox& texbox, const Vector2D& textBufferSize, const double padding, LabelAnchor anchor);
int SIDE_LENGTH = 3;
int GAP_WIDTH = 5;
CHyprColor BG_COLOR = CHyprColor{0.1, 0.1, 0.1, 1.0};
bool show_label = false;
int fontSize = 24;
LabelAnchor labelAnchor = LabelAnchor::TOP_LEFT;
bool damageDirty = false;
@ -58,6 +80,7 @@ class COverview {
int64_t workspaceID = -1;
PHLWORKSPACE pWorkspace;
CBox box;
SP<CTexture> textTex;
};
Vector2D lastMousePosLocal = Vector2D{};