WIP Window Movement

Code is bad but it does mostly work
This commit is contained in:
outfoxxed 2023-04-13 14:12:48 -07:00
parent 0e854287ff
commit 0bdf28f3bc
No known key found for this signature in database
GPG key ID: 4C88A185FB89301E
3 changed files with 366 additions and 69 deletions

View file

@ -1,14 +1,5 @@
#include "Hy3Layout.hpp" #include "Hy3Layout.hpp"
#include "src/Window.hpp"
#include "src/debug/Log.hpp"
#include "src/helpers/Vector2D.hpp"
#include "src/helpers/Workspace.hpp"
#include "src/managers/XWaylandManager.hpp"
#include "src/managers/input/InputManager.hpp"
#include "src/render/Renderer.hpp"
#include <cairo/cairo.h>
#include <ctime>
#include <memory>
#include <src/Compositor.hpp> #include <src/Compositor.hpp>
Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) {} Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) {}
@ -23,6 +14,8 @@ Hy3NodeData::Hy3NodeData(Hy3GroupData group): type(Hy3NodeData::Group) {
new(&this->as_group) Hy3GroupData(std::move(group)); new(&this->as_group) Hy3GroupData(std::move(group));
} }
Hy3NodeData::Hy3NodeData(Hy3GroupLayout layout): Hy3NodeData(Hy3GroupData(layout)) {}
Hy3NodeData::~Hy3NodeData() { Hy3NodeData::~Hy3NodeData() {
switch (this->type) { switch (this->type) {
case Hy3NodeData::Window: case Hy3NodeData::Window:
@ -80,6 +73,18 @@ Hy3NodeData& Hy3NodeData::operator=(const Hy3NodeData& from) {
return *this; return *this;
} }
Hy3NodeData& Hy3NodeData::operator=(CWindow* window) {
*this = Hy3NodeData(window);
return *this;
}
Hy3NodeData& Hy3NodeData::operator=(Hy3GroupLayout layout) {
*this = Hy3NodeData(layout);
return *this;
}
bool Hy3NodeData::operator==(const Hy3NodeData& rhs) const { bool Hy3NodeData::operator==(const Hy3NodeData& rhs) const {
if (this->type != rhs.type) return false; if (this->type != rhs.type) return false;
switch (this->type) { switch (this->type) {
@ -185,6 +190,59 @@ void Hy3Node::recalcSizePosRecursive(bool force) {
} }
} }
bool Hy3GroupData::hasChild(Hy3Node* node) {
Debug::log(LOG, "Searching for child %p of %p", this, node);
for (auto child: this->children) {
if (child == node) return true;
if (child->data.type == Hy3NodeData::Group) {
if (child->data.as_group.hasChild(node)) return true;
}
}
return false;
}
bool Hy3Node::removeChild(Hy3Node* child, bool childSwallows) {
if (this->data.type != Hy3NodeData::Group) return false;
Hy3GroupData& group = this->data.as_group;
if (group.children.remove(child)) {
if (group.children.empty()) {
Debug::log(LOG, "Group %p is empty, removing", this);
this->parent->removeChild(this, childSwallows);
this->layout->nodes.remove(*this);
} else if (childSwallows && group.children.size() == 1) {
auto remaining = group.children.front();
Debug::log(LOG, "Group %p has only one child(%p), swallowing", this, remaining);
swapNodeData(*this, *remaining);
this->layout->nodes.remove(*remaining);
}
return true;
}
return false;
}
void swapNodeData(Hy3Node& a, Hy3Node& b) {
Hy3NodeData aData = std::move(a.data);
a.data = b.data;
b.data = aData;
if (a.data.type == Hy3NodeData::Group) {
for (auto child: a.data.as_group.children) {
child->parent = &a;
}
}
if (b.data.type == Hy3NodeData::Group) {
for (auto child: b.data.as_group.children) {
child->parent = &b;
}
}
}
int Hy3Layout::getWorkspaceNodeCount(const int& id) { int Hy3Layout::getWorkspaceNodeCount(const int& id) {
int count = 0; int count = 0;
@ -341,7 +399,7 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window) {
} else { } else {
if ((opening_into = this->getWorkspaceRootGroup(window->m_iWorkspaceID)) == nullptr) { if ((opening_into = this->getWorkspaceRootGroup(window->m_iWorkspaceID)) == nullptr) {
this->nodes.push_back({ this->nodes.push_back({
.data = Hy3NodeData(Hy3GroupData(Hy3GroupLayout::SplitH)), .data = Hy3GroupLayout::SplitH,
.position = monitor->vecPosition, .position = monitor->vecPosition,
.size = monitor->vecSize, .size = monitor->vecSize,
.workspace_id = window->m_iWorkspaceID, .workspace_id = window->m_iWorkspaceID,
@ -359,7 +417,7 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window) {
this->nodes.push_back({ this->nodes.push_back({
.parent = opening_into, .parent = opening_into,
.data = Hy3NodeData(window), .data = window,
.workspace_id = window->m_iWorkspaceID, .workspace_id = window->m_iWorkspaceID,
.layout = this, .layout = this,
}); });
@ -422,7 +480,7 @@ void Hy3Layout::onWindowFocusChange(CWindow* window) {
&& node->parent->data.as_group.children.size() == 1) && node->parent->data.as_group.children.size() == 1)
{ {
auto parent = node->parent; auto parent = node->parent;
std::swap(parent->data, node->data); swapNodeData(*parent, *node);
this->nodes.remove(*node); this->nodes.remove(*node);
node = parent; node = parent;
} }
@ -455,40 +513,6 @@ void Hy3Layout::fullscreenRequestForWindow(CWindow* pWindow, eFullscreenMode mod
} }
std::any Hy3Layout::layoutMessage(SLayoutMessageHeader header, std::string content) { std::any Hy3Layout::layoutMessage(SLayoutMessageHeader header, std::string content) {
if (header.pWindow == nullptr) return "";
auto* node = this->getNodeFromWindow(header.pWindow);
if (node == nullptr) return "";
if (content == "splith" || content == "splitv") {
Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
if (content == "splitv") {
layout = Hy3GroupLayout::SplitV;
}
if (node->parent != nullptr
&& node->parent->data.as_group.children.size() == 1
&& (node->parent->data.as_group.layout == Hy3GroupLayout::SplitH
|| node->parent->data.as_group.layout == Hy3GroupLayout::SplitV))
{
node->parent->data.as_group.layout = layout;
node->parent->recalcSizePosRecursive();
return "";
}
Hy3NodeData node_data = Hy3NodeData(Hy3GroupData(layout));
std::swap(node->data, node_data);
this->nodes.push_back({
.parent = node,
.data = node_data,
.workspace_id = node->workspace_id,
.layout = this,
});
node->data.as_group.children.push_back(&this->nodes.back());
node->recalcSizePosRecursive();
}
return ""; return "";
} }
@ -497,10 +521,12 @@ SWindowRenderLayoutHints Hy3Layout::requestRenderHints(CWindow* pWindow) {
} }
void Hy3Layout::switchWindows(CWindow* pWindowA, CWindow* pWindowB) { void Hy3Layout::switchWindows(CWindow* pWindowA, CWindow* pWindowB) {
Debug::log(LOG, "SwitchWindows: %p %p", pWindowA, pWindowB);
; // empty ; // empty
} }
void Hy3Layout::alterSplitRatio(CWindow* pWindow, float delta, bool exact) { void Hy3Layout::alterSplitRatio(CWindow* pWindow, float delta, bool exact) {
Debug::log(LOG, "AlterSplitRatio: %p %f", pWindow, delta);
; // empty ; // empty
} }
@ -523,3 +549,225 @@ void Hy3Layout::onEnable() {
void Hy3Layout::onDisable() { void Hy3Layout::onDisable() {
} }
void Hy3Layout::makeGroupOn(CWindow* window, Hy3GroupLayout layout) {
auto* node = this->getNodeFromWindow(window);
if (node == nullptr) return;
if (node->parent->data.as_group.children.size() == 1
&& (node->parent->data.as_group.layout == Hy3GroupLayout::SplitH
|| node->parent->data.as_group.layout == Hy3GroupLayout::SplitV))
{
node->parent->data.as_group.layout = layout;
node->parent->recalcSizePosRecursive();
return;
}
this->nodes.push_back({
.parent = node,
.data = node->data.as_window,
.workspace_id = node->workspace_id,
.layout = this,
});
node->data = layout;
node->data.as_group.children.push_back(&this->nodes.back());
node->recalcSizePosRecursive();
return;
}
void Hy3Layout::shiftWindow(CWindow* window, ShiftDirection direction) {
Debug::log(LOG, "ShiftWindow %p %d", window, direction);
auto* node = this->getNodeFromWindow(window);
if (node == nullptr) return;
auto& group = node->parent->data.as_group;
enum class ShiftAction {
ShiftUp,
ShiftDown,
BreakGroup,
};
ShiftAction action;
switch (group.layout) {
case Hy3GroupLayout::SplitH:
case Hy3GroupLayout::Tabbed:
switch (direction) {
case ShiftDirection::Left:
action = ShiftAction::ShiftUp;
break;
case ShiftDirection::Right:
action = ShiftAction::ShiftDown;
break;
case ShiftDirection::Up:
case ShiftDirection::Down:
action = ShiftAction::BreakGroup;
break;
}
break;
case Hy3GroupLayout::SplitV:
switch (direction) {
case ShiftDirection::Up:
action = ShiftAction::ShiftUp;
break;
case ShiftDirection::Down:
action = ShiftAction::ShiftDown;
break;
case ShiftDirection::Left:
case ShiftDirection::Right:
action = ShiftAction::BreakGroup;
break;
}
break;
}
Debug::log(LOG, "ShiftWindow 2 %d", action);
Hy3Node* target_group;
switch (action) {
case ShiftAction::ShiftUp:
if (node == group.children.front()) {
action = ShiftAction::BreakGroup;
} else {
auto iter = std::find(group.children.begin(), group.children.end(), node);
auto target = *std::prev(iter);
if (target->data.type == Hy3NodeData::Group) {
target_group = target;
goto entergroup;
} else {
swapNodeData(*node, *target);
}
}
break;
case ShiftAction::ShiftDown:
if (node == group.children.back()) {
action = ShiftAction::BreakGroup;
} else {
auto iter = std::find(group.children.begin(), group.children.end(), node);
auto target = *std::next(iter);
if (target->data.type == Hy3NodeData::Group) {
target_group = target;
goto entergroup;
} else {
swapNodeData(*node, *target);
}
}
break;
case ShiftAction::BreakGroup:
break;
}
if (action != ShiftAction::BreakGroup) {
node->parent->recalcSizePosRecursive();
return;
}
goto noentergroup;
entergroup: {
auto common_parent = this->findCommonParentNode(*target_group, *node);
if (common_parent == nullptr) {
Debug::log(ERR, "Could not find common parent of nodes %p, %p while moving", target_group, node);
return;
}
auto* parent = node->parent;
node->parent = target_group;
node->size_ratio = 1.0;
Hy3GroupData& target_group_data = target_group->data.as_group;
switch (target_group_data.layout) {
case Hy3GroupLayout::SplitH:
case Hy3GroupLayout::Tabbed:
switch (direction) {
case ShiftDirection::Left:
target_group_data.children.push_back(node);
break;
case ShiftDirection::Right:
case ShiftDirection::Up:
case ShiftDirection::Down:
target_group_data.children.push_front(node);
break;
}
break;
case Hy3GroupLayout::SplitV:
switch (direction) {
case ShiftDirection::Up:
target_group_data.children.push_back(node);
break;
case ShiftDirection::Down:
case ShiftDirection::Left:
case ShiftDirection::Right:
target_group_data.children.push_front(node);
break;
}
break;
}
// this might cause the target group to get swallowed and invalidate its pointers,
// so its done after touching it.
parent->removeChild(node, true);
common_parent->recalcSizePosRecursive();
return;
}
noentergroup:
// break out of group
Hy3Node* breakout_origin = node->parent;
target_group = breakout_origin->parent;
// break parents until we reach one going the shift direction or nullptr
while (target_group != nullptr
&& !(((target_group->data.as_group.layout == Hy3GroupLayout::SplitH
|| target_group->data.as_group.layout == Hy3GroupLayout::Tabbed)
&& (direction == ShiftDirection::Left || direction == ShiftDirection::Right))
|| (target_group->data.as_group.layout == Hy3GroupLayout::SplitV
&& (direction == ShiftDirection::Up || direction == ShiftDirection::Down))))
{
breakout_origin = target_group;
target_group = breakout_origin->parent;
}
if (target_group != nullptr) {
auto* parent = node->parent;
node->parent = target_group;
node->size_ratio = 1.0;
bool insert_after = direction == ShiftDirection::Right || direction == ShiftDirection::Down;
Hy3GroupData& target_group_data = target_group->data.as_group;
auto iter = std::find(target_group_data.children.begin(), target_group_data.children.end(), breakout_origin);
if (insert_after) {
iter = std::next(iter);
}
target_group_data.children.insert(iter, node);
parent->removeChild(node, true);
target_group->recalcSizePosRecursive();
}
return;
}
Hy3Node* Hy3Layout::findCommonParentNode(Hy3Node& a, Hy3Node& b) {
Hy3Node* last_node = nullptr;
Hy3Node* searcher = &a;
while (searcher != nullptr) {
if (searcher->data.type == Hy3NodeData::Group) {
for (auto child: searcher->data.as_group.children) {
if (last_node == child) continue; // dont rescan already scanned tree
if (child == &b) return searcher;
if (child->data.type == Hy3NodeData::Group && child->data.as_group.hasChild(&b)) {
return searcher;
}
}
}
last_node = searcher;
searcher = searcher->parent;
}
return nullptr;
}

View file

@ -12,14 +12,32 @@ enum class Hy3GroupLayout {
Tabbed, Tabbed,
}; };
enum class ShiftDirection {
Left,
Up,
Down,
Right,
};
struct Hy3GroupData { struct Hy3GroupData {
Hy3GroupLayout layout = Hy3GroupLayout::SplitH; Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
std::list<Hy3Node*> children; std::list<Hy3Node*> children;
bool hasChild(Hy3Node* child);
Hy3GroupData(Hy3GroupLayout layout); Hy3GroupData(Hy3GroupLayout layout);
private:
Hy3GroupData(Hy3GroupData&&) = default;
Hy3GroupData(const Hy3GroupData&) = default;
friend class Hy3NodeData;
}; };
struct Hy3NodeData { void swapNodeData(Hy3Node& a, Hy3Node& b);
class Hy3NodeData {
public:
enum { Group, Window } type; enum { Group, Window } type;
union { union {
Hy3GroupData as_group; Hy3GroupData as_group;
@ -29,12 +47,19 @@ struct Hy3NodeData {
bool operator==(const Hy3NodeData&) const; bool operator==(const Hy3NodeData&) const;
Hy3NodeData(); Hy3NodeData();
Hy3NodeData(CWindow* window); ~Hy3NodeData();
Hy3NodeData(Hy3GroupData group); Hy3NodeData(CWindow*);
Hy3NodeData(Hy3GroupLayout);
Hy3NodeData& operator=(CWindow*);
Hy3NodeData& operator=(Hy3GroupLayout);
//private: - I give up, C++ wins
Hy3NodeData(Hy3GroupData);
Hy3NodeData(const Hy3NodeData&); Hy3NodeData(const Hy3NodeData&);
Hy3NodeData(Hy3NodeData&&); Hy3NodeData(Hy3NodeData&&);
Hy3NodeData& operator=(const Hy3NodeData&); Hy3NodeData& operator=(const Hy3NodeData&);
~Hy3NodeData();
friend void swapNodeData(Hy3Node&, Hy3Node&);
}; };
struct Hy3Node { struct Hy3Node {
@ -48,6 +73,11 @@ struct Hy3Node {
Hy3Layout* layout = nullptr; Hy3Layout* layout = nullptr;
void recalcSizePosRecursive(bool force = false); void recalcSizePosRecursive(bool force = false);
// remove a child node, returns true on success.
// fails if not a group
// if only a single child node remains && childSwallows, replace this group with said child.
// if no children remain, remove this node from its parent.
bool removeChild(Hy3Node* child, bool childSwallows = false);
bool operator==(const Hy3Node&) const; bool operator==(const Hy3Node&) const;
}; };
@ -72,9 +102,12 @@ public:
virtual void onEnable(); virtual void onEnable();
virtual void onDisable(); virtual void onDisable();
void makeGroupOn(CWindow*, Hy3GroupLayout);
void shiftWindow(CWindow*, ShiftDirection);
Hy3Node* findCommonParentNode(Hy3Node&, Hy3Node&);
private: private:
// std::list is used over std::vector because it does not invalidate references
// when mutated.
std::list<Hy3Node> nodes; std::list<Hy3Node> nodes;
CWindow* lastActiveWindow = nullptr; CWindow* lastActiveWindow = nullptr;

View file

@ -10,28 +10,44 @@ APICALL EXPORT std::string PLUGIN_API_VERSION() {
return HYPRLAND_API_VERSION; return HYPRLAND_API_VERSION;
} }
void splith(std::string) { // return a window if a window action makes sense now
SLayoutMessageHeader header; CWindow* window_for_action() {
header.pWindow = g_pCompositor->m_pLastWindow; if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return nullptr;
if (!header.pWindow) return; auto* window = g_pCompositor->m_pLastWindow;
const auto workspace = g_pCompositor->getWorkspaceByID(header.pWindow->m_iWorkspaceID); if (!window) return nullptr;
if (workspace->m_bHasFullscreenWindow) return;
g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "splith"); const auto workspace = g_pCompositor->getWorkspaceByID(window->m_iWorkspaceID);
if (workspace->m_bHasFullscreenWindow) return nullptr;
return window;
} }
void splitv(std::string) { void dispatch_makegroup(std::string arg) {
SLayoutMessageHeader header; CWindow* window = window_for_action();
header.pWindow = g_pCompositor->m_pLastWindow; if (window == nullptr) return;
if (!header.pWindow) return; if (arg == "h") {
g_Hy3Layout->makeGroupOn(window, Hy3GroupLayout::SplitH);
} else if (arg == "v") {
g_Hy3Layout->makeGroupOn(window, Hy3GroupLayout::SplitV);
}
}
const auto workspace = g_pCompositor->getWorkspaceByID(header.pWindow->m_iWorkspaceID); void dispatch_movewindow(std::string arg) {
if (workspace->m_bHasFullscreenWindow) return; CWindow* window = window_for_action();
if (window == nullptr) return;
g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "splitv"); if (arg == "l") {
g_Hy3Layout->shiftWindow(window, ShiftDirection::Left);
} else if (arg == "u") {
g_Hy3Layout->shiftWindow(window, ShiftDirection::Up);
} else if (arg == "d") {
g_Hy3Layout->shiftWindow(window, ShiftDirection::Down);
} else if (arg == "r") {
g_Hy3Layout->shiftWindow(window, ShiftDirection::Right);
}
} }
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
@ -40,8 +56,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
g_Hy3Layout = std::make_unique<Hy3Layout>(); g_Hy3Layout = std::make_unique<Hy3Layout>();
HyprlandAPI::addLayout(PHANDLE, "hy3", g_Hy3Layout.get()); HyprlandAPI::addLayout(PHANDLE, "hy3", g_Hy3Layout.get());
HyprlandAPI::addDispatcher(PHANDLE, "splith", splith); HyprlandAPI::addDispatcher(PHANDLE, "hy3_makegroup", dispatch_makegroup);
HyprlandAPI::addDispatcher(PHANDLE, "splitv", splitv); HyprlandAPI::addDispatcher(PHANDLE, "hy3_movewindow", dispatch_movewindow);
return {"hy3", "i3 like layout for hyprland", "outfoxxed", "0.1"}; return {"hy3", "i3 like layout for hyprland", "outfoxxed", "0.1"};
} }