Diff
1diff --git a/CHANGELOG.md b/CHANGELOG.md
2index 43a8dbb1e4a7dd0f8a0d2d08f6aa7ee038784400..f23978f187f0a42b48fd34d7ed2b4840376d682e 100644
3--- a/CHANGELOG.md
4+++ b/CHANGELOG.md
5@@ -1,5 +1,11 @@
6 # Changelog
7
8+# Upcoming
9+
10+- Added configurable borders to tabs
11+- Renamed `hy3:tabs:rounding` to `hy3:tabs:radius`.
12+- Changed default tab style
13+
14 # hl0.46.0 and before
15
16 - Only compatibility fixes.
17diff --git a/CMakeLists.txt b/CMakeLists.txt
18index 832d21fb7b0298e122daa95f8d386285975410f3..59297b5ca850b34b6b86628cc8292e5c6b10c3ad 100644
19--- a/CMakeLists.txt
20+++ b/CMakeLists.txt
21@@ -20,8 +20,19 @@ add_library(hy3 SHARED
22 src/Hy3Node.cpp
23 src/TabGroup.cpp
24 src/SelectionHook.cpp
25+ src/shaders.cpp
26+ src/render.cpp
27 )
28
29+configure_file(src/simple_quad.vert ${CMAKE_CURRENT_BINARY_DIR}/src/simple_quad.vert COPYONLY)
30+file(READ ${CMAKE_CURRENT_BINARY_DIR}/src/simple_quad.vert SHADER_SIMPLE_QUAD)
31+
32+configure_file(src/border_rect.frag ${CMAKE_CURRENT_BINARY_DIR}/src/border_rect.frag COPYONLY)
33+file(READ ${CMAKE_CURRENT_BINARY_DIR}/src/border_rect.frag SHADER_BORDER_RECT)
34+
35+configure_file(src/shader_content.hpp.in src/shader_content.hpp @ONLY)
36+target_include_directories(hy3 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src)
37+
38 option(HY3_NO_VERSION_CHECK "Disable hyprland version check" FALSE)
39
40 if (HY3_NO_VERSION_CHECK)
41diff --git a/README.md b/README.md
42index ab2eaa13edaff425a787247013c1ac4f6ef8edcf..740215e64d5f8d37b04e08f4b8c99dd85c79f23d 100644
43--- a/README.md
44+++ b/README.md
45@@ -240,22 +240,22 @@ plugin {
46 # tab group settings
47 tabs {
48 # height of the tab bar
49- height = <int> # default: 15
50+ height = <int> # default: 22
51
52 # padding between the tab bar and its focused node
53- padding = <int> # default: 5
54+ padding = <int> # default: 6
55
56 # the tab bar should animate in/out from the top instead of below the window
57 from_top = <bool> # default: false
58
59- # rounding of tab bar corners
60- rounding = <int> # default: 3
61+ # radius of tab bar corners
62+ radius = <int> # default: 6
63
64 # render the window title on the bar
65 render_text = <bool> # default: true
66
67 # center the window title
68- text_center = <bool> # default: false
69+ text_center = <bool> # default: true
70
71 # font to render the window title with
72 text_font = <string> # default: Sans
73@@ -266,23 +266,20 @@ plugin {
74 # left padding of the window title
75 text_padding = <int> # default: 3
76
77- # active tab bar segment color
78- col.active = <color> # default: 0xff32b4ff
79+ # active tab bar segment colors
80+ col.active = <color> # default: rgba(50a0e0b0)
81+ col.border.active = <color> # default: rgba(70b0c0b0)
82+ col.text.active = <color> # default: rgba(000000ff)
83
84- # urgent tab bar segment color
85- col.urgent = <color> # default: 0xffff4f4f
86+ # urgent tab bar segment colors
87+ col.urgent = <color> # default: rgba(ff4f4fb0)
88+ col.border.urgent = <color> # default: rgba(ff8080b0)
89+ col.text.urgent = <color> # default: rgba(000000ff)
90
91- # inactive tab bar segment color
92- col.inactive = <color> # default: 0x80808080
93-
94- # active tab bar text color
95- col.text.active = <color> # default: 0xff000000
96-
97- # urgent tab bar text color
98- col.text.urgent = <color> # default: 0xff000000
99-
100- # inactive tab bar text color
101- col.text.inactive = <color> # default: 0xff000000
102+ # inactive tab bar segment colors
103+ col.inactive = <color> # default: rgba(30303090)
104+ col.border.inactive = <color> # default: rgba(505050b0)
105+ col.text.inactive = <color> # default: rgba(ffffffff)
106 }
107
108 # autotiling settings
109diff --git a/src/TabGroup.cpp b/src/TabGroup.cpp
110index 3071826e753778874c71048c7d3352d7708c1367..01b0695e3412e75557a73933fb8a4448c3249283 100644
111--- a/src/TabGroup.cpp
112+++ b/src/TabGroup.cpp
113@@ -1,5 +1,6 @@
114 #include "TabGroup.hpp"
115
116+#include <GLES2/gl2.h>
117 #include <cairo/cairo.h>
118 #include <hyprland/src/Compositor.hpp>
119 #include <hyprland/src/desktop/DesktopTypes.hpp>
120@@ -15,6 +16,7 @@
121
122 #include "globals.hpp"
123 #include "log.hpp"
124+#include "render.hpp"
125
126 // This is a workaround CHyprColor not having working arithmetic operator...
127 static inline CHyprColor
128@@ -120,18 +122,24 @@ bool Hy3TabBarEntry::shouldRemove() {
129 void Hy3TabBarEntry::render(float scale, CBox& box, float opacity_mul) {
130 auto opacity = opacity_mul * this->fade_opacity.value();
131
132- static const auto s_rounding = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:rounding");
133+ // clang-format off
134+ static const auto s_radius = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:radius");
135+ static const auto border_width = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:border_width");
136 static const auto col_active = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.active");
137+ static const auto col_border_active = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.border.active");
138 static const auto col_urgent = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.urgent");
139+ static const auto col_border_urgent = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.border.urgent");
140 static const auto col_inactive = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.inactive");
141+ static const auto col_border_inactive = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.border.inactive");
142+ // clang-format on
143
144- auto rounding =
145- std::min((double) *s_rounding * scale, std::min(box.width * 0.5, box.height * 0.5));
146+ auto radius = std::min((double) *s_radius * scale, std::min(box.width * 0.5, box.height * 0.5));
147
148 auto focused = this->focused.value();
149 auto urgent = this->urgent.value();
150 auto inactive = 1.0f - (focused + urgent);
151- CHyprColor c = merge_colors(
152+
153+ auto color = merge_colors(
154 focused,
155 CHyprColor(*col_active),
156 urgent,
157@@ -140,9 +148,24 @@ void Hy3TabBarEntry::render(float scale, CBox& box, float opacity_mul) {
158 CHyprColor(*col_inactive)
159 );
160
161- c.a *= opacity;
162+ auto border_color = merge_colors(
163+ focused,
164+ CHyprColor(*col_border_active),
165+ urgent,
166+ CHyprColor(*col_border_urgent),
167+ inactive,
168+ CHyprColor(*col_border_inactive)
169+ );
170+
171+ color.a *= opacity;
172+ border_color.a *= opacity;
173+
174+ box.round();
175+
176+ // sometimes enabled before our renderer is called
177+ glDisable(GL_SCISSOR_TEST);
178
179- g_pHyprOpenGL->renderRect(&box, c, rounding);
180+ Hy3Render::renderBorderRect(box, color, border_color, *border_width, radius);
181 this->renderText(scale, box, opacity);
182 }
183
184diff --git a/src/border_rect.frag b/src/border_rect.frag
185new file mode 100644
186index 0000000000000000000000000000000000000000..e2d6684aa8721bcb98f2a6e5f07bc11dfda2c468
187--- /dev/null
188+++ b/src/border_rect.frag
189@@ -0,0 +1,53 @@
190+precision highp float;
191+varying highp vec4 v_color;
192+varying highp vec2 v_texcoord;
193+
194+uniform highp vec4 fillColor;
195+uniform highp vec4 borderColor;
196+uniform highp vec2 pixelSize;
197+uniform float outerRadius;
198+uniform float borderWidth;
199+
200+void main() {
201+ highp vec2 pixCoord = v_texcoord * pixelSize;
202+ highp vec2 cornerDist = min(pixCoord, pixelSize - pixCoord);
203+
204+ gl_FragColor = fillColor;
205+
206+ if (cornerDist.x <= outerRadius && cornerDist.y <= outerRadius) {
207+ highp vec2 vcornerDist = vec2(outerRadius) - cornerDist;
208+ float distSq = vcornerDist.x * vcornerDist.x + vcornerDist.y * vcornerDist.y;
209+
210+ // See https://github.com/hyprwm/Hyprland/blob/e75e2cdac79417ffdbbbe903f72668953483a4e7/src/render/shaders/SharedValues.hpp#L3
211+ const float SMOOTHING_CONSTANT = 0.58758063398831095317;
212+ const float SMOOTHING_CONSTANT_2X = SMOOTHING_CONSTANT * 2.0;
213+
214+ float innerRadius = outerRadius - borderWidth;
215+ float outerTest1 = outerRadius + SMOOTHING_CONSTANT_2X;
216+ float outerTest2 = outerRadius - SMOOTHING_CONSTANT_2X;
217+ float innerTest1 = innerRadius + SMOOTHING_CONSTANT_2X;
218+ float innerTest2 = innerRadius - SMOOTHING_CONSTANT_2X;
219+
220+ float dist;
221+ bool calculatedDist = false;
222+
223+ if (distSq > outerTest1 * outerTest1) {
224+ discard;
225+ } else if (distSq > outerTest2 * outerTest2) {
226+ dist = sqrt(distSq);
227+ calculatedDist = true;
228+ float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - outerRadius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT_2X));
229+ gl_FragColor = borderColor * normalized;
230+ } else if (distSq > innerTest2 * innerTest2) {
231+ gl_FragColor = borderColor;
232+ }
233+
234+ if (distSq > innerTest2 * innerTest2 && distSq <= innerTest1 * innerTest1) {
235+ if (!calculatedDist) dist = sqrt(distSq);
236+ float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - innerRadius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT_2X));
237+ gl_FragColor = gl_FragColor * (1.0 - normalized) + fillColor * normalized;
238+ }
239+ } else if (cornerDist.x <= borderWidth || cornerDist.y <= borderWidth) {
240+ gl_FragColor = borderColor;
241+ }
242+}
243diff --git a/src/main.cpp b/src/main.cpp
244index 96a36cb582cf4f64491db68d060cec01cb5ceede..87c39b0b54d1058b63c55b7b2d20740b4048f009 100644
245--- a/src/main.cpp
246+++ b/src/main.cpp
247@@ -1,4 +1,5 @@
248 #include <hyprland/src/Compositor.hpp>
249+#include <hyprland/src/config/ConfigDataValues.hpp>
250 #include <hyprland/src/plugins/PluginAPI.hpp>
251 #include <hyprland/src/version.h>
252
253@@ -27,11 +28,10 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
254 selection_hook::init();
255
256 #define CONF(NAME, TYPE, VALUE) \
257- HyprlandAPI::addConfigValue( \
258- PHANDLE, \
259- "plugin:hy3:" NAME, \
260- Hyprlang::CConfigValue((Hyprlang::TYPE) VALUE) \
261- )
262+ HyprlandAPI::addConfigValue(PHANDLE, "plugin:hy3:" NAME, Hyprlang::CConfigValue((TYPE) VALUE))
263+
264+ using Hyprlang::INT;
265+ using Hyprlang::STRING;
266
267 // general
268 CONF("no_gaps_when_only", INT, 0);
269@@ -40,21 +40,25 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
270 CONF("tab_first_window", INT, 0);
271
272 // tabs
273- CONF("tabs:height", INT, 15);
274+ CONF("tabs:height", INT, 22);
275 CONF("tabs:padding", INT, 5);
276 CONF("tabs:from_top", INT, 0);
277- CONF("tabs:rounding", INT, 3);
278+ CONF("tabs:radius", INT, 6);
279+ CONF("tabs:border_width", INT, 2);
280 CONF("tabs:render_text", INT, 1);
281- CONF("tabs:text_center", INT, 0);
282+ CONF("tabs:text_center", INT, 1);
283 CONF("tabs:text_font", STRING, "Sans");
284 CONF("tabs:text_height", INT, 8);
285 CONF("tabs:text_padding", INT, 3);
286- CONF("tabs:col.active", INT, 0xff32b4ff);
287- CONF("tabs:col.urgent", INT, 0xffff4f4f);
288- CONF("tabs:col.inactive", INT, 0x80808080);
289+ CONF("tabs:col.active", INT, 0xb050a0e0);
290+ CONF("tabs:col.border.active", INT, 0xb070b0c0);
291+ CONF("tabs:col.urgent", INT, 0xb0ff4f4f);
292+ CONF("tabs:col.border.urgent", INT, 0xb0ff8080);
293+ CONF("tabs:col.inactive", INT, 0x90303030);
294+ CONF("tabs:col.border.inactive", INT, 0xb0505050);
295 CONF("tabs:col.text.active", INT, 0xff000000);
296 CONF("tabs:col.text.urgent", INT, 0xff000000);
297- CONF("tabs:col.text.inactive", INT, 0xff000000);
298+ CONF("tabs:col.text.inactive", INT, 0xffffffff);
299
300 // autotiling
301 CONF("autotile:enable", INT, 0);
302diff --git a/src/render.cpp b/src/render.cpp
303new file mode 100644
304index 0000000000000000000000000000000000000000..db30cea440f777252aaec9e330fe6488ce46e7d3
305--- /dev/null
306+++ b/src/render.cpp
307@@ -0,0 +1,68 @@
308+#include "render.hpp"
309+
310+#include <GLES2/gl2.h>
311+#include <hyprland/src/helpers/math/Math.hpp>
312+#include <hyprland/src/render/OpenGL.hpp>
313+
314+#include "shaders.hpp"
315+
316+void Hy3Render::renderBorderRect(
317+ const CBox& box,
318+ const CHyprColor& fillColor,
319+ const CHyprColor& borderColor,
320+ int borderWidth,
321+ int radius
322+) {
323+ static auto& shader = Hy3Shaders::instance()->border_rect;
324+ auto& rdata = g_pHyprOpenGL->m_RenderData;
325+
326+ auto rbox = box;
327+ rdata.renderModif.applyToBox(rbox);
328+
329+ auto matrix = rdata.monitorProjection.projectBox(
330+ rbox,
331+ wlTransformToHyprutils(invertTransform(rdata.pMonitor->transform)),
332+ rbox.rot
333+ );
334+
335+ auto glMatrix = rdata.projection.copy().multiply(matrix);
336+
337+ glUseProgram(shader.program);
338+
339+#ifndef GLES2
340+ glUniformMatrix3fv(shader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
341+#else
342+ glMatrix.transpose();
343+ glUniformMatrix3fv(shader.proj, 1, GL_FALSE, glMatrix.getMatrix().data());
344+#endif
345+
346+ // premultiplied
347+ glUniform4f(
348+ shader.fillColor,
349+ fillColor.r * fillColor.a,
350+ fillColor.g * fillColor.a,
351+ fillColor.b * fillColor.a,
352+ fillColor.a
353+ );
354+
355+ if (borderWidth != 0) {
356+ glUniform4f(
357+ shader.borderColor,
358+ borderColor.r * borderColor.a,
359+ borderColor.g * borderColor.a,
360+ borderColor.b * borderColor.a,
361+ borderColor.a
362+ );
363+ }
364+
365+ glUniform2f(shader.pixelSize, rbox.w, rbox.h);
366+ glUniform1f(shader.outerRadius, radius);
367+ glUniform1f(shader.borderWidth, borderWidth);
368+
369+ glVertexAttribPointer(shader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
370+ glEnableVertexAttribArray(shader.posAttrib);
371+
372+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
373+
374+ glDisableVertexAttribArray(shader.posAttrib);
375+}
376diff --git a/src/render.hpp b/src/render.hpp
377new file mode 100644
378index 0000000000000000000000000000000000000000..6efdf1d1e27dc60163d88c4be424d1f5b0744236
379--- /dev/null
380+++ b/src/render.hpp
381@@ -0,0 +1,14 @@
382+#pragma once
383+#include <hyprland/src/helpers/Color.hpp>
384+#include <hyprutils/math/Box.hpp>
385+
386+class Hy3Render {
387+public:
388+ static void renderBorderRect(
389+ const CBox& box,
390+ const CHyprColor& fillColor,
391+ const CHyprColor& borderColor,
392+ int borderWidth,
393+ int radius
394+ );
395+};
396diff --git a/src/shader_content.hpp.in b/src/shader_content.hpp.in
397new file mode 100644
398index 0000000000000000000000000000000000000000..6e242a452fe1910df06988b1c4ba869ae2418239
399--- /dev/null
400+++ b/src/shader_content.hpp.in
401@@ -0,0 +1,4 @@
402+#include <string_view>
403+
404+constexpr std::string_view SHADER_SIMPLE_QUAD = R"(@SHADER_SIMPLE_QUAD@)";
405+constexpr std::string_view SHADER_BORDER_RECT_FRAG = R"(@SHADER_BORDER_RECT@)";
406diff --git a/src/shaders.cpp b/src/shaders.cpp
407new file mode 100644
408index 0000000000000000000000000000000000000000..88f02f407f817da6cb0608f0dcdfb676e533c059
409--- /dev/null
410+++ b/src/shaders.cpp
411@@ -0,0 +1,90 @@
412+#include "shaders.hpp"
413+#include <csignal>
414+
415+#include <GLES2/gl2.h>
416+#include <hyprland/src/render/OpenGL.hpp>
417+
418+#include "shader_content.hpp"
419+
420+Hy3Shaders::Hy3Shaders() {
421+ {
422+ auto& s = this->border_rect;
423+ s.program = Hy3Shaders::createProgram(SHADER_SIMPLE_QUAD, SHADER_BORDER_RECT_FRAG);
424+ s.posAttrib = glGetAttribLocation(s.program, "pos");
425+ s.proj = glGetUniformLocation(s.program, "proj");
426+ s.pixelSize = glGetUniformLocation(s.program, "pixelSize");
427+ s.fillColor = glGetUniformLocation(s.program, "fillColor");
428+ s.borderColor = glGetUniformLocation(s.program, "borderColor");
429+ s.outerRadius = glGetUniformLocation(s.program, "outerRadius");
430+ s.borderWidth = glGetUniformLocation(s.program, "borderWidth");
431+ }
432+}
433+
434+Hy3Shaders::~Hy3Shaders() { glDeleteProgram(this->border_rect.program); }
435+
436+Hy3Shaders* Hy3Shaders::instance() {
437+ static auto* INSTANCE = new Hy3Shaders();
438+ return INSTANCE;
439+}
440+
441+GLuint Hy3Shaders::createProgram(const std::string_view vert, const std::string_view frag) {
442+ auto vshader = compileShader(GL_VERTEX_SHADER, vert);
443+ auto fshader = compileShader(GL_FRAGMENT_SHADER, frag);
444+
445+ auto program = glCreateProgram();
446+ glAttachShader(program, vshader);
447+ glAttachShader(program, fshader);
448+ glLinkProgram(program);
449+
450+ glDetachShader(program, vshader);
451+ glDetachShader(program, fshader);
452+ glDeleteShader(vshader);
453+ glDeleteShader(fshader);
454+
455+ GLint ok;
456+ glGetProgramiv(program, GL_LINK_STATUS, &ok);
457+
458+ if (ok != GL_TRUE) {
459+ Hy3Shaders::logShaderError(program, true);
460+ }
461+
462+ return program;
463+}
464+
465+GLuint Hy3Shaders::compileShader(GLuint type, const std::string_view src) {
466+ auto srcdata = src.data();
467+ GLint srclen = src.length();
468+
469+ auto shader = glCreateShader(type);
470+ glShaderSource(shader, 1, &srcdata, &srclen);
471+ glCompileShader(shader);
472+
473+ GLint ok;
474+ glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
475+
476+ if (ok != GL_TRUE) {
477+ Hy3Shaders::logShaderError(shader, false);
478+ }
479+
480+ return shader;
481+}
482+
483+void Hy3Shaders::logShaderError(GLuint shader, bool program) {
484+ GLint logBufferLength = 0;
485+
486+ if (program) glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &logBufferLength);
487+ else glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logBufferLength);
488+
489+ auto* logBuffer = new char[logBufferLength];
490+
491+ if (program) glGetProgramInfoLog(shader, logBufferLength, &logBufferLength, logBuffer);
492+ else glGetShaderInfoLog(shader, logBufferLength, &logBufferLength, logBuffer);
493+
494+ if (program) {
495+ Debug::log(ERR, "Failed to link shader program: {}", logBuffer);
496+ } else {
497+ Debug::log(ERR, "Failed to compile shader: {}", logBuffer);
498+ }
499+
500+ raise(SIGABRT);
501+}
502diff --git a/src/shaders.hpp b/src/shaders.hpp
503new file mode 100644
504index 0000000000000000000000000000000000000000..1fb39a003909a8f1e97bdd8f46a5d64f5db980ca
505--- /dev/null
506+++ b/src/shaders.hpp
507@@ -0,0 +1,30 @@
508+#pragma once
509+
510+#include <string_view>
511+
512+#include <GLES2/gl2.h>
513+#include <hyprland/src/render/OpenGL.hpp>
514+
515+class Hy3Shaders {
516+public:
517+ struct {
518+ GLuint program;
519+ GLuint posAttrib;
520+ GLint proj;
521+ GLint fillColor;
522+ GLint borderColor;
523+ GLint pixelSize;
524+ GLint outerRadius;
525+ GLint borderWidth;
526+ } border_rect;
527+
528+ static Hy3Shaders* instance();
529+
530+private:
531+ Hy3Shaders();
532+ ~Hy3Shaders();
533+
534+ static GLuint createProgram(const std::string_view vert, const std::string_view frag);
535+ static GLuint compileShader(GLuint type, const std::string_view src);
536+ static void logShaderError(GLuint shader, bool program);
537+};
538diff --git a/src/simple_quad.vert b/src/simple_quad.vert
539new file mode 100644
540index 0000000000000000000000000000000000000000..0398200e810f607690ba2a0880851c7f8377da64
541--- /dev/null
542+++ b/src/simple_quad.vert
543@@ -0,0 +1,11 @@
544+attribute highp vec2 pos;
545+attribute highp vec2 texcoord;
546+
547+uniform mat3 proj;
548+
549+varying highp vec2 v_texcoord;
550+
551+void main() {
552+ gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
553+ v_texcoord = pos;
554+}