5d6b415c7f02194cbb30d534b30cd91e7032eaa3

Author
outfoxxed <outfoxxed@outfoxxed.me>
Committer
outfoxxed <outfoxxed@outfoxxed.me>
Date

Message

Initial work on tab groups

Diff

This diff is truncated to protect this page.

  1diff --git a/CMakeLists.txt b/CMakeLists.txt
  2index 64e3c51fb5d7c87c832a8421f5c9e8679e4e523c..5ad5ce0cfeaecc65293900920107a56ee1928868 100644
  3--- a/CMakeLists.txt
  4+++ b/CMakeLists.txt
  5@@ -11,11 +11,12 @@ if(CMAKE_EXPORT_COMPILE_COMMANDS)
  6 endif()
  7 
  8 find_package(PkgConfig REQUIRED)
  9-pkg_check_modules(DEPS REQUIRED hyprland pixman-1 libdrm)
 10+pkg_check_modules(DEPS REQUIRED hyprland pixman-1 libdrm pango pangocairo)
 11 
 12 add_library(hy3 SHARED
 13 	src/main.cpp
 14 	src/Hy3Layout.cpp
 15+	src/TabGroup.cpp
 16 	src/SelectionHook.cpp
 17 )
 18 
 19diff --git a/flake.lock b/flake.lock
 20index c8699520113bfe5c1d2e89b9afd501fe2fc4c070..2764e8b3b414f521f602bc6b50ca932b84b46451 100644
 21--- a/flake.lock
 22+++ b/flake.lock
 23@@ -8,11 +8,11 @@
 24         "xdph": "xdph"
 25       },
 26       "locked": {
 27-        "lastModified": 1682603803,
 28-        "narHash": "sha256-NY9nVAdB7UyInu2vPx/DIUVNZ83t4RdP16QY9DTIn4s=",
 29+        "lastModified": 1684167111,
 30+        "narHash": "sha256-0JKyr8WOpcXJP5XaLnUSI7e1d7N5Rcpyf72+N4ZEtjo=",
 31         "owner": "hyprwm",
 32         "repo": "Hyprland",
 33-        "rev": "f23455e592bca14e0abd9249de467cc71cd2850e",
 34+        "rev": "5b84b0fb445bc4485510bba516c84141aaeafd04",
 35         "type": "github"
 36       },
 37       "original": {
 38@@ -44,11 +44,11 @@
 39     },
 40     "nixpkgs": {
 41       "locked": {
 42-        "lastModified": 1682453498,
 43-        "narHash": "sha256-WoWiAd7KZt5Eh6n+qojcivaVpnXKqBsVgpixpV2L9CE=",
 44+        "lastModified": 1683014792,
 45+        "narHash": "sha256-6Va9iVtmmsw4raBc3QKvQT2KT/NGRWlvUlJj46zN8B8=",
 46         "owner": "NixOS",
 47         "repo": "nixpkgs",
 48-        "rev": "c8018361fa1d1650ee8d4b96294783cf564e8a7f",
 49+        "rev": "1a411f23ba299db155a5b45d5e145b85a7aafc42",
 50         "type": "github"
 51       },
 52       "original": {
 53diff --git a/flake.nix b/flake.nix
 54index 75e1b450c2f93bc549514fae7405d25fe43521bb..af84a4587bd4afeca47eba8dccc85ef47b776931 100644
 55--- a/flake.nix
 56+++ b/flake.nix
 57@@ -15,8 +15,10 @@
 58 
 59         nativeBuildInputs = with pkgs; [ cmake pkg-config ];
 60 
 61-        buildInputs = [
 62+        buildInputs = with pkgs; [
 63           hyprland.packages.${system}.hyprland.dev
 64+          pango
 65+          cairo
 66         ] ++ hyprland.packages.${system}.hyprland.buildInputs;
 67 
 68         # no noticeable impact on performance and greatly assists debugging
 69diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp
 70index 68e04a816e7866d9b91eaad732c60cc63a1ac7a8..6ec06ec83d90838bb409399ffd3ea8d16cf0f825 100644
 71--- a/src/Hy3Layout.cpp
 72+++ b/src/Hy3Layout.cpp
 73@@ -41,18 +41,6 @@ Hy3NodeData::~Hy3NodeData() {
 74 	}
 75 }
 76 
 77-Hy3NodeData::Hy3NodeData(const Hy3NodeData& from): type(from.type) {
 78-	Debug::log(LOG, "Copy CTor type matches? %d is group? %d", this->type == from.type, this->type == Hy3NodeData::Group);
 79-	switch (from.type) {
 80-	case Hy3NodeData::Window:
 81-		this->as_window = from.as_window;
 82-		break;
 83-	case Hy3NodeData::Group:
 84-		new(&this->as_group) Hy3GroupData(from.as_group);
 85-		break;
 86-	}
 87-}
 88-
 89 Hy3NodeData::Hy3NodeData(Hy3NodeData&& from): type(from.type) {
 90 	Debug::log(LOG, "Move CTor type matches? %d is group? %d", this->type == from.type, this->type == Hy3NodeData::Group);
 91 	switch (from.type) {
 92@@ -65,7 +53,7 @@ Hy3NodeData::Hy3NodeData(Hy3NodeData&& from): type(from.type) {
 93 	}
 94 }
 95 
 96-Hy3NodeData& Hy3NodeData::operator=(const Hy3NodeData& from) {
 97+Hy3NodeData& Hy3NodeData::operator=(Hy3NodeData&& from) {
 98 	Debug::log(LOG, "operator= type matches? %d is group? %d", this->type == from.type, this->type == Hy3NodeData::Group);
 99 	if (this->type == Hy3NodeData::Group) {
100 		this->as_group.~Hy3GroupData();
101@@ -78,7 +66,7 @@ Hy3NodeData& Hy3NodeData::operator=(const Hy3NodeData& from) {
102 		this->as_window = from.as_window;
103 		break;
104 	case Hy3NodeData::Group:
105-		new(&this->as_group) Hy3GroupData(from.as_group);
106+		new(&this->as_group) Hy3GroupData(std::move(from.as_group));
107 		break;
108 	}
109 
110@@ -121,36 +109,44 @@ void Hy3Node::recalcSizePosRecursive(bool force) {
111 			errorNotif();
112 		}
113 
114-		double distortOut;
115-		double distortIn;
116+		double distort_out;
117+		double distort_in;
118+		double tab_height_offset;
119 
120-		const auto* gaps_in     = &g_pConfigManager->getConfigValuePtr("general:gaps_in")->intValue;
121-		const auto* gaps_out    = &g_pConfigManager->getConfigValuePtr("general:gaps_out")->intValue;
122+		static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue;
123+		static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue;
124+		static const auto* tab_bar_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:bar_height")->intValue;
125+
126+		tab_height_offset = *gaps_in * 2 + *tab_bar_height;
127 
128 		if (gaps_in > gaps_out) {
129-			distortOut = *gaps_out - 1.0;
130+			distort_out = *gaps_out - 1.0;
131 		} else {
132-			distortOut = *gaps_in - 1.0;
133+			distort_out = *gaps_in - 1.0;
134 		}
135 
136-		if (distortOut < 0) distortOut = 0.0;
137+		if (distort_out < 0) distort_out = 0.0;
138 
139-		distortIn = *gaps_in * 2;
140+		distort_in = *gaps_in * 2;
141 
142 		switch (group->layout) {
143 		case Hy3GroupLayout::SplitH:
144-			child->position.x = this->position.x - distortOut;
145-			child->size.x = this->size.x - distortIn;
146+			child->position.x = this->position.x - distort_out;
147+			child->size.x = this->size.x - distort_in;
148 			child->position.y = this->position.y;
149 			child->size.y = this->size.y;
150 			break;
151 		case Hy3GroupLayout::SplitV:
152-			child->position.y = this->position.y - distortOut;
153-			child->size.y = this->size.y - distortIn;
154+			child->position.y = this->position.y - distort_out;
155+			child->size.y = this->size.y - distort_in;
156 			child->position.x = this->position.x;
157 			child->size.x = this->size.x;
158+			break;
159 		case Hy3GroupLayout::Tabbed:
160-			// TODO
161+			child->position.y = this->position.y + tab_height_offset;
162+			child->size.y = this->size.y - tab_height_offset;
163+			child->position.x = this->position.x;
164+			child->size.x = this->size.x;
165 			break;
166 		}
167 
168@@ -182,6 +178,7 @@ void Hy3Node::recalcSizePosRecursive(bool force) {
169 			offset += child->size.x;
170 			child->position.y = this->position.y;
171 			child->size.y = this->size.y;
172+			//child->setHidden(false);
173diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp
174index 7b34ae69b6c1710c08461a545d1a13051b9e114a..8e044d284c56b9dcfe2d2a5c7b9bf76f81a9c009 100644
175--- a/src/Hy3Layout.hpp
176+++ b/src/Hy3Layout.hpp
177@@ -1,5 +1,8 @@
178 #pragma once
179 
180+struct Hy3Node;
181+#include "TabGroup.hpp"
182+
183 #include <list>
184 #include <hyprland/src/layout/IHyprLayout.hpp>
185 
186@@ -23,6 +26,7 @@ struct Hy3GroupData {
187 	Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
188 	std::list<Hy3Node*> children;
189 	Hy3Node* lastFocusedChild = nullptr;
190+	std::unique_ptr<Hy3TabGroup> tab_bar;
191 
192 	bool hasChild(Hy3Node* child);
193 
194@@ -30,7 +34,6 @@ struct Hy3GroupData {
195 
196 private:
197 	Hy3GroupData(Hy3GroupData&&) = default;
198-	Hy3GroupData(const Hy3GroupData&) = default;
199 
200 	friend class Hy3NodeData;
201 };
202@@ -54,9 +57,8 @@ public:
203 
204 	//private: - I give up, C++ wins
205 	Hy3NodeData(Hy3GroupData);
206-	Hy3NodeData(const Hy3NodeData&);
207 	Hy3NodeData(Hy3NodeData&&);
208-	Hy3NodeData& operator=(const Hy3NodeData&);
209+	Hy3NodeData& operator=(Hy3NodeData&&);
210 };
211 
212 struct Hy3Node {
213@@ -76,6 +78,9 @@ struct Hy3Node {
214 	void raiseToTop();
215 	Hy3Node* getFocusedNode();
216 	void updateDecos();
217+	void setHidden(bool hidden);
218+	bool isUrgent();
219+	std::string getTitle();
220 
221 	bool operator==(const Hy3Node&) const;
222 
223@@ -126,6 +131,8 @@ public:
224 	Hy3Node* getWorkspaceRootGroup(const int&);
225 	Hy3Node* getWorkspaceFocusedNode(const int&);
226 
227+	static void renderHook(void*, std::any);
228+
229 	std::list<Hy3Node> nodes;
230 private:
231 	struct {
232diff --git a/src/TabGroup.cpp b/src/TabGroup.cpp
233new file mode 100644
234index 0000000000000000000000000000000000000000..d40f63ac93406cfd095acc55282f97460dde909e
235--- /dev/null
236+++ b/src/TabGroup.cpp
237@@ -0,0 +1,198 @@
238+#include "globals.hpp"
239+#include "TabGroup.hpp"
240+#include "Hy3Layout.hpp"
241+
242+#include <hyprland/src/Compositor.hpp>
243+#include <cairo/cairo.h>
244+
245+void Hy3TabBar::updateWithGroupEntries(Hy3Node& group_node) {
246+	if (group_node.data.type != Hy3NodeData::Group) return;
247+	auto& group = group_node.data.as_group;
248+
249+	auto entries_iter = this->entries.begin();
250+	auto group_iter = group.children.begin();
251+
252+	auto* root_node = &group_node;
253+	while (root_node->parent != nullptr) root_node = root_node->parent;
254+	Hy3Node* focused_node = root_node->getFocusedNode();
255+
256+	while (entries_iter != this->entries.end()) {
257+		if (group_iter == group.children.end()) {
258+			needs_redraw = true;
259+
260+			while (entries_iter != this->entries.end()) {
261+				entries_iter = this->entries.erase(entries_iter);
262+			}
263+
264+			return;
265+		};
266+
267+		auto& entry = *entries_iter;
268+		auto& node = **group_iter;
269+
270+		std::string title = node.getTitle();
271+		bool urgent = node.isUrgent();
272+		bool focused = focused_node == &group_node
273+		|| focused_node == &node
274+			|| (node.data.type == Hy3NodeData::Group && node.data.as_group.hasChild(focused_node));
275+
276+		if (entry.urgent != urgent
277+				|| entry.focused != focused
278+				|| entry.window_title != title)
279+			this->needs_redraw = true;
280+
281+		entry.window_title = std::move(title);
282+		entry.urgent = urgent;
283+		entry.focused = focused;
284+
285+		entries_iter = std::next(entries_iter);
286+		group_iter = std::next(group_iter);
287+	}
288+
289+	while (group_iter != group.children.end()) {
290+		needs_redraw = true;
291+
292+		auto& node = **group_iter;
293+
294+		this->entries.push_back({
295+			.window_title = node.getTitle(),
296+			.urgent = node.isUrgent(),
297+			.focused = focused_node == &group_node
298+				|| focused_node == &node
299+				|| (node.data.type == Hy3NodeData::Group && node.data.as_group.hasChild(focused_node)),
300+		});
301+
302+		group_iter = std::next(group_iter);
303+	}
304+}
305+
306+void Hy3TabBar::setPos(Vector2D pos) {
307+	if (pos == this->pos) return;
308+	needs_redraw = true;
309+	this->pos = pos;
310+}
311+
312+void Hy3TabBar::setSize(Vector2D size) {
313+	this->size = size;
314+}
315+
316+void Hy3TabBar::prepareTexture() {
317+	auto bar_width = this->size.x;
318+	auto bar_height = this->size.y;
319+
320+	if (needs_redraw || this->texture.m_iTexID == 0) {
321+		static const auto* rounding_setting = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:rounding")->intValue;
322+		auto rounding = *rounding_setting;
323+
324+		auto cairo_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, this->size.x, this->size.y);
325+		auto cairo = cairo_create(cairo_surface);
326+
327+		// clear pixmap
328+		cairo_save(cairo);
329+		cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
330+		cairo_paint(cairo);
331+		cairo_restore(cairo);
332+
333+		auto swidth = (double) bar_width / (double) this->entries.size();
334+		size_t i = 0;
335+
336+		for (auto& entry: this->entries) {
337diff --git a/src/TabGroup.hpp b/src/TabGroup.hpp
338new file mode 100644
339index 0000000000000000000000000000000000000000..08d0de67542dceb7056895882fb1dbcabdaacd5a
340--- /dev/null
341+++ b/src/TabGroup.hpp
342@@ -0,0 +1,54 @@
343+#pragma once
344+
345+#include <hyprland/src/helpers/AnimatedVariable.hpp>
346+#include <hyprland/src/helpers/Vector2D.hpp>
347+#include <vector>
348+
349+class Hy3TabGroup;
350+
351+#include "Hy3Layout.hpp"
352+#include <hyprland/src/render/Texture.hpp>
353+
354+struct Hy3TabBarEntry {
355+	std::string window_title;
356+	bool urgent = false;
357+	bool focused = false;
358+};
359+
360+class Hy3TabBar {
361+public:
362+	CTexture texture;
363+
364+	void updateWithGroupEntries(Hy3Node&);
365+	void setPos(Vector2D);
366+	void setSize(Vector2D);
367+
368+	// Redraw the texture if necessary, and bind it to GL_TEXTURE_2D
369+	void prepareTexture();
370+private:
371+	bool needs_redraw = true;
372+
373+	std::vector<Hy3TabBarEntry> entries;
374+	// scaled pos/size
375+	Vector2D pos;
376+	Vector2D size;
377+};
378+
379+class Hy3TabGroup {
380+public:
381+	Hy3TabBar bar;
382+	CAnimatedVariable pos;
383+	CAnimatedVariable size;
384+
385+	// initialize a group with the given node. UB if node is not a group.
386+	Hy3TabGroup(Hy3Node&);
387+
388+	// update tab bar with node position and data. UB if node is not a group.
389+	void updateWithGroup(Hy3Node&);
390+	// render the scaled tab bar on the current monitor.
391+	void renderTabBar();
392+
393+private:
394+	// moving a Hy3TabGroup will unregister any active animations
395+	Hy3TabGroup(Hy3TabGroup&&) = delete;
396+};
397diff --git a/src/main.cpp b/src/main.cpp
398index f299fe92a18322710fded7165d77cb1897035d9a..309303b838f6221f7bdccb287417e507dc2df6ca 100644
399--- a/src/main.cpp
400+++ b/src/main.cpp
401@@ -45,6 +45,8 @@ void dispatch_makegroup(std::string arg) {
402 		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitH);
403 	} else if (arg == "v") {
404 		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitV);
405+	} else if (arg == "tab") {
406+		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::Tabbed);
407 	} else if (arg == "opposite") {
408 		g_Hy3Layout->makeOppositeGroupOnWorkspace(workspace);
409 	}
410@@ -104,6 +106,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
411 	selection_hook::init();
412 
413 	HyprlandAPI::addConfigValue(PHANDLE, "plugin:hy3:no_gaps_when_only", SConfigValue{.intValue = 0});
414+	HyprlandAPI::addConfigValue(PHANDLE, "plugin:hy3:tabs:bar_height", SConfigValue{.intValue = 15});
415+	HyprlandAPI::addConfigValue(PHANDLE, "plugin:hy3:tabs:rounding", SConfigValue{.intValue = 3});
416 
417 	g_Hy3Layout = std::make_unique<Hy3Layout>();
418 	HyprlandAPI::addLayout(PHANDLE, "hy3", g_Hy3Layout.get());