hyprland-plugins/hyprbars/barDeco.cpp
2026-05-12 13:21:43 +01:00

678 lines
28 KiB
C++

#include "barDeco.hpp"
#include <hyprland/src/Compositor.hpp>
#include <hyprland/src/desktop/state/FocusState.hpp>
#include <hyprland/src/desktop/view/Window.hpp>
#include <hyprland/src/helpers/MiscFunctions.hpp>
#include <hyprland/src/managers/SeatManager.hpp>
#include <hyprland/src/managers/input/InputManager.hpp>
#include <hyprland/src/render/Renderer.hpp>
#include <hyprland/src/config/ConfigManager.hpp>
#include <hyprland/src/config/shared/animation/AnimationTree.hpp>
#include <hyprland/src/config/shared/parserUtils/ParserUtils.hpp>
#include <hyprland/src/config/supplementary/executor/Executor.hpp>
#include <hyprland/src/config/shared/actions/ConfigActions.hpp>
#include <hyprland/src/managers/animation/AnimationManager.hpp>
#include <hyprland/src/protocols/LayerShell.hpp>
#include <hyprland/src/event/EventBus.hpp>
#include <hyprland/src/layout/LayoutManager.hpp>
#include <hyprland/src/render/OpenGL.hpp>
#include "globals.hpp"
#include "BarPassElement.hpp"
#include <climits>
using namespace Render::GL;
static CHyprColor configColor(Config::INTEGER color) {
return CHyprColor{static_cast<uint64_t>(color)};
}
CHyprBar::CHyprBar(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow) {
m_pWindow = pWindow;
const auto PMONITOR = pWindow->m_monitor.lock();
PMONITOR->m_scheduledRecalc = true;
// button events
m_pMouseButtonCallback = Event::bus()->m_events.input.mouse.button.listen([&](IPointer::SButtonEvent e, Event::SCallbackInfo& info) { onMouseButton(info, e); });
m_pTouchDownCallback = Event::bus()->m_events.input.touch.down.listen([&](ITouch::SDownEvent e, Event::SCallbackInfo& info) { onTouchDown(info, e); });
m_pTouchUpCallback = Event::bus()->m_events.input.touch.up.listen([&](ITouch::SUpEvent e, Event::SCallbackInfo& info) { onTouchUp(info, e); });
// move events
m_pTouchMoveCallback = Event::bus()->m_events.input.touch.motion.listen([&](ITouch::SMotionEvent e, Event::SCallbackInfo& info) { onTouchMove(info, e); });
m_pMouseMoveCallback = Event::bus()->m_events.input.mouse.move.listen([&](Vector2D c, Event::SCallbackInfo& info) { onMouseMove(c); });
g_pAnimationManager->createAnimation(configColor(g_pGlobalState->config.barColor->value()), m_cRealBarColor, Config::animationTree()->getAnimationPropertyConfig("border"),
pWindow, AVARDAMAGE_NONE);
m_cRealBarColor->setUpdateCallback([&](auto) { damageEntire(); });
}
CHyprBar::~CHyprBar() {
std::erase(g_pGlobalState->bars, m_self);
}
SDecorationPositioningInfo CHyprBar::getPositioningInfo() {
const auto HEIGHT = g_pGlobalState->config.barHeight->value();
const auto ENABLED = g_pGlobalState->config.enabled->value();
const auto PRECEDENCE = g_pGlobalState->config.barPrecedenceOverBorder->value();
SDecorationPositioningInfo info;
info.policy = m_hidden ? DECORATION_POSITION_ABSOLUTE : DECORATION_POSITION_STICKY;
info.edges = DECORATION_EDGE_TOP;
info.priority = PRECEDENCE ? 10005 : 5000;
info.reserved = true;
info.desiredExtents = {{0, m_hidden || !ENABLED ? 0 : HEIGHT}, {0, 0}};
return info;
}
void CHyprBar::onPositioningReply(const SDecorationPositioningReply& reply) {
if (reply.assignedGeometry.size() != m_bAssignedBox.size())
m_bWindowSizeChanged = true;
m_bAssignedBox = reply.assignedGeometry;
}
std::string CHyprBar::getDisplayName() {
return "Hyprbar";
}
bool CHyprBar::inputIsValid() {
if (!g_pGlobalState->config.enabled->value())
return false;
if (!m_pWindow->m_workspace || !m_pWindow->m_workspace->isVisible() || !g_pInputManager->m_exclusiveLSes.empty() ||
(g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(m_pWindow->wlSurface()->resource())))
return false;
const auto WINDOWATCURSOR = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),
Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);
auto focusState = Desktop::focusState();
auto window = focusState->window();
auto monitor = focusState->monitor();
if (WINDOWATCURSOR != m_pWindow && m_pWindow != window)
return false;
// check if input is on top or overlay shell layers
auto PMONITOR = monitor;
PHLLS foundSurface = nullptr;
Vector2D surfaceCoords;
// check top layer
g_pCompositor->vectorToLayerSurface(g_pInputManager->getMouseCoordsInternal(), &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &surfaceCoords, &foundSurface);
if (foundSurface)
return false;
// check overlay layer
g_pCompositor->vectorToLayerSurface(g_pInputManager->getMouseCoordsInternal(), &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &surfaceCoords,
&foundSurface);
if (foundSurface)
return false;
return true;
}
void CHyprBar::onMouseButton(Event::SCallbackInfo& info, IPointer::SButtonEvent e) {
if (!inputIsValid())
return;
if (e.state != WL_POINTER_BUTTON_STATE_PRESSED) {
handleUpEvent(info);
return;
}
handleDownEvent(info, std::nullopt);
}
void CHyprBar::onTouchDown(Event::SCallbackInfo& info, ITouch::SDownEvent e) {
// Don't do anything if you're already grabbed a window with another finger
if (!inputIsValid() || e.touchID != 0)
return;
handleDownEvent(info, e);
}
void CHyprBar::onTouchUp(Event::SCallbackInfo& info, ITouch::SUpEvent e) {
if (!m_bDragPending || !m_bTouchEv || e.touchID != m_touchId)
return;
handleUpEvent(info);
}
void CHyprBar::onMouseMove(Vector2D coords) {
// ensure proper redraws of button icons on hover when using hardware cursors
if (g_pGlobalState->config.iconOnHover->value())
damageOnButtonHover();
if (!m_bDragPending || m_bTouchEv || !validMapped(m_pWindow) || m_touchId != 0)
return;
m_bDragPending = false;
handleMovement();
}
void CHyprBar::onTouchMove(Event::SCallbackInfo& info, ITouch::SMotionEvent e) {
if (!m_bDragPending || !m_bTouchEv || !validMapped(m_pWindow) || e.touchID != m_touchId)
return;
auto PMONITOR = m_pWindow->m_monitor.lock();
PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor();
const auto COORDS = Vector2D(PMONITOR->m_position.x + e.pos.x * PMONITOR->m_size.x, PMONITOR->m_position.y + e.pos.y * PMONITOR->m_size.y);
if (!m_bDraggingThis) {
// Initial setup for dragging a window.
g_pKeybindManager->m_dispatchers["setfloating"]("activewindow");
g_pKeybindManager->m_dispatchers["resizewindowpixel"]("exact 50% 50%,activewindow");
// pin it so you can change workspaces while dragging a window
g_pKeybindManager->m_dispatchers["pin"]("activewindow");
}
g_pKeybindManager->m_dispatchers["movewindowpixel"](std::format("exact {} {},activewindow", (int)(COORDS.x - (assignedBoxGlobal().w / 2)), (int)COORDS.y));
m_bDraggingThis = true;
}
void CHyprBar::handleDownEvent(Event::SCallbackInfo& info, std::optional<ITouch::SDownEvent> touchEvent) {
m_bTouchEv = touchEvent.has_value();
if (m_bTouchEv)
m_touchId = touchEvent.value().touchID;
const auto PWINDOW = m_pWindow.lock();
auto COORDS = cursorRelativeToBar();
if (m_bTouchEv) {
ITouch::SDownEvent e = touchEvent.value();
auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : "");
PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor();
COORDS = Vector2D(PMONITOR->m_position.x + e.pos.x * PMONITOR->m_size.x, PMONITOR->m_position.y + e.pos.y * PMONITOR->m_size.y) - assignedBoxGlobal().pos();
}
const auto HEIGHT = g_pGlobalState->config.barHeight->value();
const auto BARBUTTONPADDING = g_pGlobalState->config.barButtonPadding->value();
const auto BARPADDING = g_pGlobalState->config.barPadding->value();
const auto ALIGNBUTTONS = g_pGlobalState->config.barButtonsAlignment->value();
const auto ON_DOUBLE_CLICK = g_pGlobalState->config.onDoubleClick->value();
const bool BUTTONSRIGHT = ALIGNBUTTONS != "left";
if (!VECINRECT(COORDS, 0, 0, assignedBoxGlobal().w, HEIGHT - 1)) {
if (m_bDraggingThis) {
if (m_bTouchEv)
g_pKeybindManager->m_dispatchers["settiled"]("activewindow");
g_pKeybindManager->m_dispatchers["mouse"]("0movewindow");
Log::logger->log(Log::DEBUG, "[hyprbars] Dragging ended on {:x}", (uintptr_t)PWINDOW.get());
}
m_bDraggingThis = false;
m_bDragPending = false;
m_bTouchEv = false;
return;
}
if (Desktop::focusState()->window() != PWINDOW)
Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_CLICK);
if (PWINDOW->m_isFloating)
g_pCompositor->changeWindowZOrder(PWINDOW, true);
info.cancelled = true;
m_bCancelledDown = true;
if (doButtonPress(BARPADDING, BARBUTTONPADDING, HEIGHT, COORDS, BUTTONSRIGHT))
return;
if (!ON_DOUBLE_CLICK.empty() &&
std::chrono::duration_cast<std::chrono::milliseconds>(Time::steadyNow() - m_lastMouseDown).count() < 400 /* Arbitrary delay I found suitable */) {
Config::Supplementary::executor()->spawn(ON_DOUBLE_CLICK);
m_bDragPending = false;
} else {
m_lastMouseDown = Time::steadyNow();
m_bDragPending = true;
}
}
void CHyprBar::handleUpEvent(Event::SCallbackInfo& info) {
if (m_pWindow.lock() != Desktop::focusState()->window())
return;
if (m_bCancelledDown)
info.cancelled = true;
m_bCancelledDown = false;
if (m_bDraggingThis) {
g_pKeybindManager->changeMouseBindMode(MBIND_INVALID);
m_bDraggingThis = false;
if (m_bTouchEv)
Config::Actions::floatWindow(Config::Actions::eTogglableAction::TOGGLE_ACTION_DISABLE);
Log::logger->log(Log::DEBUG, "[hyprbars] Dragging ended on {:x}", (uintptr_t)m_pWindow.lock().get());
}
m_bDragPending = false;
m_bTouchEv = false;
m_touchId = 0;
}
void CHyprBar::handleMovement() {
g_pKeybindManager->changeMouseBindMode(MBIND_MOVE);
m_bDraggingThis = true;
Log::logger->log(Log::DEBUG, "[hyprbars] Dragging initiated on {:x}", (uintptr_t)m_pWindow.lock().get());
return;
}
bool CHyprBar::doButtonPress(Config::INTEGER barPadding, Config::INTEGER barButtonPadding, Config::INTEGER barHeight, Vector2D COORDS, const bool BUTTONSRIGHT) {
//check if on a button
float offset = barPadding;
for (auto& b : g_pGlobalState->buttons) {
const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, barHeight};
Vector2D currentPos = Vector2D{(BUTTONSRIGHT ? BARBUF.x - barButtonPadding - b.size - offset : offset), (BARBUF.y - b.size) / 2.0}.floor();
if (VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + b.size + barButtonPadding, currentPos.y + b.size)) {
// hit on close
g_pKeybindManager->m_dispatchers["exec"](b.cmd);
return true;
}
offset += barButtonPadding + b.size;
}
return false;
}
void CHyprBar::renderBarTitle(const Vector2D& bufferSize, const float scale) {
const auto COLORVAL = g_pGlobalState->config.textColor->value();
const auto SIZE = g_pGlobalState->config.barTextSize->value();
const auto FONT = g_pGlobalState->config.barTextFont->value();
const auto ALIGN = g_pGlobalState->config.barTextAlign->value();
const auto BARPADDING = g_pGlobalState->config.barPadding->value();
const auto BARBUTTONPADDING = g_pGlobalState->config.barButtonPadding->value();
float buttonSizes = BARBUTTONPADDING;
for (auto& b : g_pGlobalState->buttons) {
buttonSizes += b.size + BARBUTTONPADDING;
}
const int scaledSize = std::round(SIZE * scale);
const auto scaledButtonsSize = buttonSizes * scale;
const auto scaledBarPadding = BARPADDING * scale;
const int paddingTotal = scaledBarPadding * 2 + scaledButtonsSize + (ALIGN != "left" ? scaledButtonsSize : 0);
const int maxWidth = std::clamp(static_cast<int>(bufferSize.x - paddingTotal), 0, INT_MAX);
if (m_szLastTitle.empty() || maxWidth < 1) {
m_pTextTex = nullptr;
return;
}
const CHyprColor COLOR = m_bForcedTitleColor.value_or(configColor(COLORVAL));
m_pTextTex = g_pHyprRenderer->renderText(m_szLastTitle, COLOR, scaledSize, false, FONT, maxWidth);
}
size_t CHyprBar::getVisibleButtonCount(Config::INTEGER barButtonPadding, Config::INTEGER barPadding, const Vector2D& bufferSize, const float scale) {
float availableSpace = bufferSize.x - barPadding * scale * 2;
size_t count = 0;
for (const auto& button : g_pGlobalState->buttons) {
const float buttonSpace = (button.size + barButtonPadding) * scale;
if (availableSpace >= buttonSpace) {
count++;
availableSpace -= buttonSpace;
} else
break;
}
return count;
}
void CHyprBar::renderBarButtons(CBox* barBox, const float scale, const float a) {
const auto BARBUTTONPADDING = g_pGlobalState->config.barButtonPadding->value();
const auto BARPADDING = g_pGlobalState->config.barPadding->value();
const auto ALIGNBUTTONS = g_pGlobalState->config.barButtonsAlignment->value();
const auto INACTIVECOLOR = g_pGlobalState->config.inactiveButtonColor->value();
const bool BUTTONSRIGHT = ALIGNBUTTONS != "left";
const auto visibleCount = getVisibleButtonCount(BARBUTTONPADDING, BARPADDING, Vector2D{barBox->w, barBox->h}, scale);
const bool INVALIDATEICONS = m_bButtonsDirty || m_bWindowSizeChanged;
int offset = BARPADDING * scale;
for (size_t i = 0; i < visibleCount; ++i) {
auto& button = g_pGlobalState->buttons[i];
const auto scaledButtonSize = button.size * scale;
const auto scaledButtonsPad = BARBUTTONPADDING * scale;
auto color = button.bgcol;
if (INACTIVECOLOR > 0) {
color = m_bWindowHasFocus ? color : configColor(INACTIVECOLOR);
if (INVALIDATEICONS && button.userfg && button.iconTex)
button.iconTex = nullptr;
}
color.a *= a;
CBox buttonBox = {barBox->x + (BUTTONSRIGHT ? barBox->w - offset - scaledButtonSize : offset), barBox->y + (barBox->h - scaledButtonSize) / 2.0, scaledButtonSize,
scaledButtonSize};
buttonBox.round();
g_pHyprOpenGL->renderRect(buttonBox, color, {.round = static_cast<int>(std::round(scaledButtonSize / 2.0)), .roundingPower = 2.F});
offset += scaledButtonsPad + scaledButtonSize;
}
}
void CHyprBar::renderBarButtonsText(CBox* barBox, const float scale, const float a) {
const auto HEIGHT = g_pGlobalState->config.barHeight->value();
const auto BARBUTTONPADDING = g_pGlobalState->config.barButtonPadding->value();
const auto BARPADDING = g_pGlobalState->config.barPadding->value();
const auto ALIGNBUTTONS = g_pGlobalState->config.barButtonsAlignment->value();
const auto ICONONHOVER = g_pGlobalState->config.iconOnHover->value();
const bool BUTTONSRIGHT = ALIGNBUTTONS != "left";
const auto visibleCount = getVisibleButtonCount(BARBUTTONPADDING, BARPADDING, Vector2D{barBox->w, barBox->h}, scale);
const auto COORDS = cursorRelativeToBar();
int offset = BARPADDING * scale;
float noScaleOffset = BARPADDING;
for (size_t i = 0; i < visibleCount; ++i) {
auto& button = g_pGlobalState->buttons[i];
const auto scaledButtonSize = button.size * scale;
const auto scaledButtonsPad = BARBUTTONPADDING * scale;
// check if hovering here
const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, HEIGHT};
Vector2D currentPos = Vector2D{(BUTTONSRIGHT ? BARBUF.x - BARBUTTONPADDING - button.size - noScaleOffset : noScaleOffset), (BARBUF.y - button.size) / 2.0}.floor();
bool hovering = VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + button.size + BARBUTTONPADDING, currentPos.y + button.size);
noScaleOffset += BARBUTTONPADDING + button.size;
if ((!button.iconTex || button.iconTex->m_texID == 0) && !button.icon.empty()) {
// render icon
auto fgcol = button.userfg ? button.fgcol : (button.bgcol.r + button.bgcol.g + button.bgcol.b < 1) ? CHyprColor(0xFFFFFFFF) : CHyprColor(0xFF000000);
button.iconTex = g_pHyprRenderer->renderText(button.icon, fgcol, std::round(button.size * 0.62 * scale), false, "sans", scaledButtonSize);
}
if (!button.iconTex || button.iconTex->m_texID == 0)
continue;
const auto iconX = barBox->x + (BUTTONSRIGHT ? barBox->width - offset - scaledButtonSize / 2.0 : offset + scaledButtonSize / 2.0) - button.iconTex->m_size.x / 2.0;
const auto iconY = barBox->y + barBox->height / 2.0 - button.iconTex->m_size.y / 2.0;
CBox pos = {iconX, iconY, button.iconTex->m_size.x, button.iconTex->m_size.y};
if (!ICONONHOVER || (ICONONHOVER && m_iButtonHoverState > 0))
g_pHyprOpenGL->renderTexture(button.iconTex, pos, {.a = a});
offset += scaledButtonsPad + scaledButtonSize;
bool currentBit = (m_iButtonHoverState & (1 << i)) != 0;
if (hovering != currentBit) {
m_iButtonHoverState ^= (1 << i);
// damage to get rid of some artifacts when icons are "hidden"
damageEntire();
}
}
}
void CHyprBar::draw(PHLMONITOR pMonitor, const float& a) {
const auto ENABLED = g_pGlobalState->config.enabled->value();
if (m_bLastEnabledState != ENABLED) {
m_bLastEnabledState = ENABLED;
g_pDecorationPositioner->repositionDeco(this);
}
if (m_hidden || !validMapped(m_pWindow) || !ENABLED)
return;
const auto PWINDOW = m_pWindow.lock();
if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault())
return;
auto data = CBarPassElement::SBarData{this, a};
g_pHyprRenderer->m_renderPass.add(makeUnique<CBarPassElement>(data));
}
void CHyprBar::renderPass(PHLMONITOR pMonitor, const float& a) {
const auto PWINDOW = m_pWindow.lock();
static auto PENABLEBLURGLOBAL = CConfigValue<Config::BOOL>("decoration:blur:enabled");
const auto BARCOLOR = g_pGlobalState->config.barColor->value();
const auto HEIGHT = g_pGlobalState->config.barHeight->value();
const auto PRECEDENCE = g_pGlobalState->config.barPrecedenceOverBorder->value();
const auto ALIGNBUTTONS = g_pGlobalState->config.barButtonsAlignment->value();
const auto ENABLETITLE = g_pGlobalState->config.barTitleEnabled->value();
const auto ENABLEBLUR = g_pGlobalState->config.barBlur->value();
const auto INACTIVECOLOR = g_pGlobalState->config.inactiveButtonColor->value();
if (INACTIVECOLOR > 0) {
bool currentWindowFocus = PWINDOW == Desktop::focusState()->window();
if (currentWindowFocus != m_bWindowHasFocus) {
m_bWindowHasFocus = currentWindowFocus;
m_bButtonsDirty = true;
}
}
const CHyprColor DEST_COLOR = m_bForcedBarColor.value_or(configColor(BARCOLOR));
if (DEST_COLOR != m_cRealBarColor->goal())
*m_cRealBarColor = DEST_COLOR;
CHyprColor color = m_cRealBarColor->value();
color.a *= a;
const bool BUTTONSRIGHT = ALIGNBUTTONS != "left";
const bool SHOULDBLUR = ENABLEBLUR && *PENABLEBLURGLOBAL && color.a < 1.F;
if (HEIGHT < 1) {
m_iLastHeight = HEIGHT;
return;
}
const auto PWORKSPACE = PWINDOW->m_workspace;
const auto WORKSPACEOFFSET = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D();
const auto ROUNDING = PWINDOW->rounding() + (PRECEDENCE ? 0 : PWINDOW->getRealBorderSize());
const auto scaledRounding = ROUNDING > 0 ? ROUNDING * pMonitor->m_scale - 2 /* idk why but otherwise it looks bad due to the gaps */ : 0;
m_seExtents = {{0, HEIGHT}, {}};
const auto DECOBOX = assignedBoxGlobal();
const auto BARBUF = DECOBOX.size() * pMonitor->m_scale;
CBox titleBarBox = {DECOBOX.x - pMonitor->m_position.x, DECOBOX.y - pMonitor->m_position.y, DECOBOX.w,
DECOBOX.h + ROUNDING * 3 /* to fill the bottom cuz we can't disable rounding there */};
titleBarBox.translate(PWINDOW->m_floatingOffset).scale(pMonitor->m_scale).round();
if (titleBarBox.w < 1 || titleBarBox.h < 1)
return;
g_pHyprOpenGL->scissor(titleBarBox);
if (ROUNDING) {
// the +1 is a shit garbage temp fix until renderRect supports an alpha matte
CBox windowBox = {PWINDOW->m_realPosition->value().x + PWINDOW->m_floatingOffset.x - pMonitor->m_position.x + 1,
PWINDOW->m_realPosition->value().y + PWINDOW->m_floatingOffset.y - pMonitor->m_position.y + 1, PWINDOW->m_realSize->value().x - 2,
PWINDOW->m_realSize->value().y - 2};
if (windowBox.w < 1 || windowBox.h < 1)
return;
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, true);
glStencilFunc(GL_ALWAYS, 1, -1);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
windowBox.translate(WORKSPACEOFFSET).scale(pMonitor->m_scale).round();
g_pHyprOpenGL->renderRect(windowBox, CHyprColor(0, 0, 0, 0), {.round = scaledRounding, .roundingPower = m_pWindow->roundingPower()});
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilFunc(GL_NOTEQUAL, 1, -1);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
}
if (SHOULDBLUR)
g_pHyprOpenGL->renderRect(titleBarBox, color, {.round = scaledRounding, .roundingPower = m_pWindow->roundingPower(), .blur = true, .blurA = a});
else
g_pHyprOpenGL->renderRect(titleBarBox, color, {.round = scaledRounding, .roundingPower = m_pWindow->roundingPower()});
// render title
if (ENABLETITLE && (m_szLastTitle != PWINDOW->m_title || m_bWindowSizeChanged || !m_pTextTex || m_pTextTex->m_texID == 0 || m_bTitleColorChanged)) {
m_szLastTitle = PWINDOW->m_title;
renderBarTitle(BARBUF, pMonitor->m_scale);
}
if (ROUNDING) {
// cleanup stencil
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, false);
glStencilMask(-1);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
}
CBox textBox = {titleBarBox.x, titleBarBox.y, (int)BARBUF.x, (int)BARBUF.y};
if (ENABLETITLE && m_pTextTex) {
const auto BARPADDING = g_pGlobalState->config.barPadding->value();
const auto BARBUTTONPADDING = g_pGlobalState->config.barButtonPadding->value();
const auto ALIGN = g_pGlobalState->config.barTextAlign->value();
float buttonSizes = BARBUTTONPADDING;
for (auto& b : g_pGlobalState->buttons) {
buttonSizes += b.size + BARBUTTONPADDING;
}
const auto scaledBorderSize = PWINDOW->getRealBorderSize() * pMonitor->m_scale;
const auto scaledButtonsSize = buttonSizes * pMonitor->m_scale;
const auto scaledBarPadding = BARPADDING * pMonitor->m_scale;
const auto xOffset = ALIGN == "left" ? std::round(scaledBarPadding + (BUTTONSRIGHT ? 0 : scaledButtonsSize)) :
std::round(((BARBUF.x - scaledBorderSize) / 2.0 - m_pTextTex->m_size.x / 2.0));
const auto yOffset = std::round((BARBUF.y - m_pTextTex->m_size.y) / 2.0);
CBox titleBox = {textBox.x + xOffset, textBox.y + yOffset, m_pTextTex->m_size.x, m_pTextTex->m_size.y};
g_pHyprOpenGL->renderTexture(m_pTextTex, titleBox, {.a = a});
}
renderBarButtons(&textBox, pMonitor->m_scale, a);
m_bButtonsDirty = false;
g_pHyprOpenGL->scissor(nullptr);
renderBarButtonsText(&textBox, pMonitor->m_scale, a);
m_bWindowSizeChanged = false;
m_bTitleColorChanged = false;
// dynamic updates change the extents
if (m_iLastHeight != HEIGHT) {
PWINDOW->layoutTarget()->recalc();
m_iLastHeight = HEIGHT;
}
}
eDecorationType CHyprBar::getDecorationType() {
return DECORATION_CUSTOM;
}
void CHyprBar::updateWindow(PHLWINDOW pWindow) {
damageEntire();
}
void CHyprBar::onConfigReloaded() {
m_bButtonsDirty = true;
m_bTitleColorChanged = true;
m_pTextTex = nullptr;
g_pDecorationPositioner->repositionDeco(this);
damageEntire();
}
void CHyprBar::damageEntire() {
g_pHyprRenderer->damageBox(assignedBoxGlobal());
}
Vector2D CHyprBar::cursorRelativeToBar() {
return g_pInputManager->getMouseCoordsInternal() - assignedBoxGlobal().pos();
}
eDecorationLayer CHyprBar::getDecorationLayer() {
return DECORATION_LAYER_UNDER;
}
uint64_t CHyprBar::getDecorationFlags() {
return DECORATION_ALLOWS_MOUSE_INPUT | (g_pGlobalState->config.barPartOfWindow->value() ? DECORATION_PART_OF_MAIN_WINDOW : 0);
}
CBox CHyprBar::assignedBoxGlobal() {
if (!validMapped(m_pWindow))
return {};
CBox box = m_bAssignedBox;
box.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_TOP, m_pWindow.lock()));
const auto PWORKSPACE = m_pWindow->m_workspace;
const auto WORKSPACEOFFSET = PWORKSPACE && !m_pWindow->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D();
return box.translate(WORKSPACEOFFSET);
}
PHLWINDOW CHyprBar::getOwner() {
return m_pWindow.lock();
}
void CHyprBar::updateRules() {
const auto PWINDOW = m_pWindow.lock();
auto prevHidden = m_hidden;
auto prevForcedTitleColor = m_bForcedTitleColor;
m_bForcedBarColor = std::nullopt;
m_bForcedTitleColor = std::nullopt;
m_hidden = false;
if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pGlobalState->nobarRuleIdx))
m_hidden = truthy(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pGlobalState->nobarRuleIdx)->effect);
if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pGlobalState->barColorRuleIdx))
m_bForcedBarColor = CHyprColor(Config::ParserUtils::parseColor(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pGlobalState->barColorRuleIdx)->effect).value_or(0));
if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pGlobalState->titleColorRuleIdx))
m_bForcedTitleColor = CHyprColor(Config::ParserUtils::parseColor(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pGlobalState->titleColorRuleIdx)->effect).value_or(0));
if (prevHidden != m_hidden)
g_pDecorationPositioner->repositionDeco(this);
if (prevForcedTitleColor != m_bForcedTitleColor)
m_bTitleColorChanged = true;
}
void CHyprBar::damageOnButtonHover() {
const auto BARPADDING = g_pGlobalState->config.barPadding->value();
const auto BARBUTTONPADDING = g_pGlobalState->config.barButtonPadding->value();
const auto HEIGHT = g_pGlobalState->config.barHeight->value();
const auto ALIGNBUTTONS = g_pGlobalState->config.barButtonsAlignment->value();
const bool BUTTONSRIGHT = ALIGNBUTTONS != "left";
float offset = BARPADDING;
const auto COORDS = cursorRelativeToBar();
for (auto& b : g_pGlobalState->buttons) {
const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, HEIGHT};
Vector2D currentPos = Vector2D{(BUTTONSRIGHT ? BARBUF.x - BARBUTTONPADDING - b.size - offset : offset), (BARBUF.y - b.size) / 2.0}.floor();
bool hover = VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + b.size + BARBUTTONPADDING, currentPos.y + b.size);
if (hover != m_bButtonHovered) {
m_bButtonHovered = hover;
damageEntire();
}
offset += BARBUTTONPADDING + b.size;
}
}