diff --git a/hyprbars/README.md b/hyprbars/README.md index a49debd..e84a2a6 100644 --- a/hyprbars/README.md +++ b/hyprbars/README.md @@ -2,7 +2,7 @@ Adds simple title bars to windows. -![preview](https://i.ibb.co/GkDTL4Q/20230228-23h20m36s-grim.png) +![preview](https://i.ibb.co/hLDRCpT/20231029-22h30m05s.png) ## Config @@ -16,8 +16,8 @@ plugin { # example buttons (R -> L) # hyprbars-button = color, size, on-click - hyprbars-button = rgb(ff4040), 10, hyprctl dispatch killactive - hyprbars-button = rgb(eeee11), 10, hyprctl dispatch fullscreen 1 + hyprbars-button = rgb(ff4040), 10, 󰖭, hyprctl dispatch killactive + hyprbars-button = rgb(eeee11), 10, , hyprctl dispatch fullscreen 1 } } ``` @@ -37,5 +37,5 @@ plugin { Use the `hyprbars-button` keyword. ```ini -hyprbars-button = color, size, on-click +hyprbars-button = color, size, icon, on-click ``` \ No newline at end of file diff --git a/hyprbars/barDeco.cpp b/hyprbars/barDeco.cpp index 7562df7..5757bab 100644 --- a/hyprbars/barDeco.cpp +++ b/hyprbars/barDeco.cpp @@ -28,6 +28,7 @@ CHyprBar::~CHyprBar() { damageEntire(); HyprlandAPI::unregisterCallback(PHANDLE, m_pMouseButtonCallback); HyprlandAPI::unregisterCallback(PHANDLE, m_pMouseMoveCallback); + std::erase(g_pGlobalState->bars, this); } bool CHyprBar::allowsInput() { @@ -113,6 +114,63 @@ void CHyprBar::onMouseMove(Vector2D coords) { } } +void CHyprBar::renderText(CTexture& out, const std::string& text, const CColor& 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 title 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_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); + + int layoutWidth, layoutHeight; + pango_layout_get_size(layout, &layoutWidth, &layoutHeight); + 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_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 CHyprBar::renderBarTitle(const Vector2D& bufferSize, const float scale) { static auto* const PCOLOR = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:col.text")->intValue; static auto* const PSIZE = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_text_size")->intValue; @@ -206,8 +264,8 @@ void CHyprBar::renderBarButtons(const Vector2D& bufferSize, const float scale) { // draw buttons int offset = scaledButtonsPad; - auto drawButton = [&](SHyprButton& pButton) -> void { - const auto scaledButtonSize = pButton.size * scale; + auto drawButton = [&](SHyprButton& button) -> void { + const auto scaledButtonSize = button.size * scale; Vector2D currentPos = Vector2D{bufferSize.x - offset - scaledButtonSize, (bufferSize.y - scaledButtonSize) / 2.0}.floor(); @@ -215,7 +273,7 @@ void CHyprBar::renderBarButtons(const Vector2D& bufferSize, const float scale) { const int Y = currentPos.y; const int RADIUS = static_cast(std::ceil(scaledButtonSize / 2.0)); - cairo_set_source_rgba(CAIRO, pButton.col.r, pButton.col.g, pButton.col.b, pButton.col.a); + cairo_set_source_rgba(CAIRO, button.col.r, button.col.g, button.col.b, button.col.a); cairo_arc(CAIRO, X, Y + RADIUS, RADIUS, 0, 2 * M_PI); cairo_fill(CAIRO); @@ -245,6 +303,37 @@ void CHyprBar::renderBarButtons(const Vector2D& bufferSize, const float scale) { cairo_surface_destroy(CAIROSURFACE); } +void CHyprBar::renderBarButtonsText(wlr_box* barBox, const float scale) { + const auto scaledButtonsPad = BUTTONS_PAD * scale; + int offset = scaledButtonsPad; + + auto drawButton = [&](SHyprButton& button) -> void { + const auto scaledButtonSize = button.size * scale; + + if (button.iconTex.m_iTexID == 0 /* icon is not rendered */ && !button.icon.empty()) { + // render icon + const Vector2D BUFSIZE = {scaledButtonSize, scaledButtonSize}; + + const bool LIGHT = button.col.r + button.col.g + button.col.b < 1; + + renderText(button.iconTex, button.icon, LIGHT ? CColor(0xFFFFFFFF) : CColor(0xFF000000), BUFSIZE, scale, scaledButtonSize * 0.62); + } + + if (button.iconTex.m_iTexID == 0) + return; + + wlr_box pos = {barBox->x + barBox->width - offset - scaledButtonSize * 1.5, barBox->y + (barBox->height - scaledButtonSize) / 2.0, scaledButtonSize, scaledButtonSize}; + + g_pHyprOpenGL->renderTexture(button.iconTex, &pos, 1); + + offset += scaledButtonsPad + scaledButtonSize; + }; + + for (auto& b : g_pGlobalState->buttons) { + drawButton(b); + } +} + void CHyprBar::draw(CMonitor* pMonitor, float a, const Vector2D& offset) { if (!g_pCompositor->windowValidMapped(m_pWindow)) return; @@ -322,12 +411,17 @@ void CHyprBar::draw(CMonitor* pMonitor, float a, const Vector2D& offset) { wlr_box textBox = {titleBarBox.x, titleBarBox.y, (int)BARBUF.x, (int)BARBUF.y}; g_pHyprOpenGL->renderTexture(m_tTextTex, &textBox, a); - renderBarButtons(BARBUF, pMonitor->scale); + if (m_bButtonsDirty || m_bWindowSizeChanged) { + renderBarButtons(BARBUF, pMonitor->scale); + m_bButtonsDirty = false; + } g_pHyprOpenGL->renderTexture(m_tButtonsTex, &textBox, a); g_pHyprOpenGL->scissor((wlr_box*)nullptr); + renderBarButtonsText(&textBox, pMonitor->scale); + m_bWindowSizeChanged = false; } diff --git a/hyprbars/barDeco.hpp b/hyprbars/barDeco.hpp index 3b2a43b..9ea5bf7 100644 --- a/hyprbars/barDeco.hpp +++ b/hyprbars/barDeco.hpp @@ -25,6 +25,8 @@ class CHyprBar : public IHyprWindowDecoration { virtual bool allowsInput(); + bool m_bButtonsDirty = true; + private: SWindowDecorationExtents m_seExtents; @@ -41,7 +43,9 @@ class CHyprBar : public IHyprWindowDecoration { Vector2D cursorRelativeToBar(); void renderBarTitle(const Vector2D& bufferSize, const float scale); + void renderText(CTexture& out, const std::string& text, const CColor& color, const Vector2D& bufferSize, const float scale, const int fontSize); void renderBarButtons(const Vector2D& bufferSize, const float scale); + void renderBarButtonsText(wlr_box* barBox, const float scale); void onMouseDown(SCallbackInfo& info, wlr_pointer_button_event* e); void onMouseMove(Vector2D coords); diff --git a/hyprbars/globals.hpp b/hyprbars/globals.hpp index 42dd3f0..2570a0f 100644 --- a/hyprbars/globals.hpp +++ b/hyprbars/globals.hpp @@ -8,10 +8,15 @@ struct SHyprButton { std::string cmd = ""; CColor col = CColor(0, 0, 0, 0); float size = 10; + std::string icon = ""; + CTexture iconTex; }; +class CHyprBar; + struct SGlobalState { std::vector buttons; + std::vector bars; }; inline std::unique_ptr g_pGlobalState; \ No newline at end of file diff --git a/hyprbars/main.cpp b/hyprbars/main.cpp index 92a8181..8c78e9f 100644 --- a/hyprbars/main.cpp +++ b/hyprbars/main.cpp @@ -19,8 +19,11 @@ void onNewWindow(void* self, std::any data) { // data is guaranteed auto* const PWINDOW = std::any_cast(data); - if (!PWINDOW->m_bX11DoesntWantBorders) - HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, new CHyprBar(PWINDOW)); + if (!PWINDOW->m_bX11DoesntWantBorders) { + CHyprBar* bar = new CHyprBar(PWINDOW); + HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, static_cast(bar)); + g_pGlobalState->bars.push_back(bar); + } } void onPreConfigReload() { @@ -30,7 +33,7 @@ void onPreConfigReload() { void onNewButton(const std::string& k, const std::string& v) { CVarList vars(v); - // hyprbars-button = color, size, action + // hyprbars-button = color, size, icon, action if (vars[0].empty() || vars[1].empty()) return; @@ -40,7 +43,11 @@ void onNewButton(const std::string& k, const std::string& v) { size = std::stof(vars[1]); } catch (std::exception& e) { return; } - g_pGlobalState->buttons.push_back(SHyprButton{vars[2], configStringToInt(vars[0]), size}); + g_pGlobalState->buttons.push_back(SHyprButton{vars[3], configStringToInt(vars[0]), size, vars[2]}); + + for (auto& b : g_pGlobalState->bars) { + b->m_bButtonsDirty = true; + } } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { @@ -72,7 +79,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { if (w->isHidden() || !w->m_bIsMapped) continue; - HyprlandAPI::addWindowDecoration(PHANDLE, w.get(), new CHyprBar(w.get())); + onNewWindow(nullptr /* unused */, std::any(w.get())); } HyprlandAPI::reloadConfig();