mirror of
https://github.com/Trensa-Organization/hy3.git
synced 2025-03-15 18:53:40 +01:00
951 lines
26 KiB
C++
951 lines
26 KiB
C++
#include <sstream>
|
|
|
|
#include <hyprland/src/Compositor.hpp>
|
|
#include <hyprland/src/helpers/Box.hpp>
|
|
#include <hyprland/src/plugins/PluginAPI.hpp>
|
|
|
|
#include "Hy3Node.hpp"
|
|
#include "globals.hpp"
|
|
|
|
const float MIN_RATIO = 0.0f;
|
|
|
|
// Hy3GroupData //
|
|
|
|
Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) {
|
|
if (layout != Hy3GroupLayout::Tabbed) {
|
|
this->previous_nontab_layout = layout;
|
|
}
|
|
}
|
|
|
|
Hy3GroupData::Hy3GroupData(Hy3GroupData&& from) {
|
|
this->layout = from.layout;
|
|
this->previous_nontab_layout = from.previous_nontab_layout;
|
|
this->children = std::move(from.children);
|
|
this->group_focused = from.group_focused;
|
|
this->expand_focused = from.expand_focused;
|
|
this->focused_child = from.focused_child;
|
|
from.focused_child = nullptr;
|
|
this->tab_bar = from.tab_bar;
|
|
from.tab_bar = nullptr;
|
|
}
|
|
|
|
Hy3GroupData::~Hy3GroupData() {
|
|
if (this->tab_bar != nullptr) this->tab_bar->bar.beginDestroy();
|
|
}
|
|
|
|
bool Hy3GroupData::hasChild(Hy3Node* node) {
|
|
for (auto child: this->children) {
|
|
if (child == node) return true;
|
|
|
|
if (child->data.type == Hy3NodeType::Group) {
|
|
if (child->data.as_group.hasChild(node)) return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Hy3GroupData::collapseExpansions() {
|
|
if (this->expand_focused == ExpandFocusType::NotExpanded) return;
|
|
this->expand_focused = ExpandFocusType::NotExpanded;
|
|
|
|
Hy3Node* node = this->focused_child;
|
|
|
|
while (node->data.type == Hy3NodeType::Group
|
|
&& node->data.as_group.expand_focused == ExpandFocusType::Stack)
|
|
{
|
|
node->data.as_group.expand_focused = ExpandFocusType::NotExpanded;
|
|
node = node->data.as_group.focused_child;
|
|
}
|
|
}
|
|
|
|
void Hy3GroupData::setLayout(Hy3GroupLayout layout) {
|
|
this->layout = layout;
|
|
|
|
if (layout != Hy3GroupLayout::Tabbed) {
|
|
this->previous_nontab_layout = layout;
|
|
}
|
|
}
|
|
|
|
void Hy3GroupData::setEphemeral(GroupEphemeralityOption ephemeral) {
|
|
switch (ephemeral) {
|
|
case GroupEphemeralityOption::Standard: this->ephemeral = false; break;
|
|
case GroupEphemeralityOption::ForceEphemeral: this->ephemeral = true; break;
|
|
case GroupEphemeralityOption::Ephemeral:
|
|
// no change
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Hy3NodeData //
|
|
|
|
Hy3NodeData::Hy3NodeData(): Hy3NodeData((CWindow*) nullptr) {}
|
|
|
|
Hy3NodeData::Hy3NodeData(CWindow* window): type(Hy3NodeType::Window) { this->as_window = window; }
|
|
|
|
Hy3NodeData::Hy3NodeData(Hy3GroupLayout layout): Hy3NodeData(Hy3GroupData(layout)) {}
|
|
|
|
Hy3NodeData::Hy3NodeData(Hy3GroupData group): type(Hy3NodeType::Group) {
|
|
new (&this->as_group) Hy3GroupData(std::move(group));
|
|
}
|
|
|
|
Hy3NodeData::Hy3NodeData(Hy3NodeData&& from): type(from.type) {
|
|
switch (from.type) {
|
|
case Hy3NodeType::Window: this->as_window = from.as_window; break;
|
|
case Hy3NodeType::Group: new (&this->as_group) Hy3GroupData(std::move(from.as_group)); break;
|
|
}
|
|
}
|
|
|
|
Hy3NodeData::~Hy3NodeData() {
|
|
switch (this->type) {
|
|
case Hy3NodeType::Window: break;
|
|
case Hy3NodeType::Group:
|
|
this->as_group.~Hy3GroupData();
|
|
|
|
// who ever thought calling the dtor after a move was a good idea?
|
|
this->type = Hy3NodeType::Window;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Hy3NodeData& Hy3NodeData::operator=(CWindow* window) {
|
|
*this = Hy3NodeData(window);
|
|
|
|
return *this;
|
|
}
|
|
|
|
Hy3NodeData& Hy3NodeData::operator=(Hy3GroupLayout layout) {
|
|
*this = Hy3NodeData(layout);
|
|
|
|
return *this;
|
|
}
|
|
|
|
Hy3NodeData& Hy3NodeData::operator=(Hy3NodeData&& from) {
|
|
if (this->type == Hy3NodeType::Group) {
|
|
this->as_group.~Hy3GroupData();
|
|
}
|
|
|
|
this->type = from.type;
|
|
|
|
switch (this->type) {
|
|
case Hy3NodeType::Window: this->as_window = from.as_window; break;
|
|
case Hy3NodeType::Group: new (&this->as_group) Hy3GroupData(std::move(from.as_group)); break;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool Hy3NodeData::operator==(const Hy3NodeData& rhs) const { return this == &rhs; }
|
|
|
|
// Hy3Node //
|
|
|
|
bool Hy3Node::operator==(const Hy3Node& rhs) const { return this->data == rhs.data; }
|
|
|
|
void Hy3Node::focus() {
|
|
this->markFocused();
|
|
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window:
|
|
this->data.as_window->setHidden(false);
|
|
g_pCompositor->focusWindow(this->data.as_window);
|
|
break;
|
|
case Hy3NodeType::Group:
|
|
g_pCompositor->focusWindow(nullptr);
|
|
this->raiseToTop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
CWindow* Hy3Node::bringToTop() {
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window:
|
|
this->markFocused();
|
|
this->data.as_window->setHidden(false);
|
|
|
|
return this->data.as_window;
|
|
case Hy3NodeType::Group:
|
|
if (this->data.as_group.layout == Hy3GroupLayout::Tabbed) {
|
|
if (this->data.as_group.focused_child != nullptr) {
|
|
return this->data.as_group.focused_child->bringToTop();
|
|
}
|
|
} else {
|
|
for (auto* node: this->data.as_group.children) {
|
|
auto* window = node->bringToTop();
|
|
if (window != nullptr) return window;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
|
|
void Hy3Node::focusWindow() {
|
|
auto* window = this->bringToTop();
|
|
if (window != nullptr) g_pCompositor->focusWindow(window);
|
|
}
|
|
|
|
void markGroupFocusedRecursive(Hy3GroupData& group) {
|
|
group.group_focused = true;
|
|
for (auto& child: group.children) {
|
|
if (child->data.type == Hy3NodeType::Group) markGroupFocusedRecursive(child->data.as_group);
|
|
}
|
|
}
|
|
|
|
void Hy3Node::markFocused() {
|
|
Hy3Node* node = this;
|
|
|
|
// undo decos for root focus
|
|
auto* root = node;
|
|
while (root->parent != nullptr) root = root->parent;
|
|
|
|
// update focus
|
|
if (this->data.type == Hy3NodeType::Group) {
|
|
markGroupFocusedRecursive(this->data.as_group);
|
|
}
|
|
|
|
auto* node2 = node;
|
|
while (node2->parent != nullptr) {
|
|
node2->parent->data.as_group.focused_child = node2;
|
|
node2->parent->data.as_group.group_focused = false;
|
|
node2 = node2->parent;
|
|
}
|
|
|
|
root->updateDecos();
|
|
}
|
|
|
|
void Hy3Node::raiseToTop() {
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window: g_pCompositor->changeWindowZOrder(this->data.as_window, true); break;
|
|
case Hy3NodeType::Group:
|
|
for (auto* child: this->data.as_group.children) {
|
|
child->raiseToTop();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Hy3Node* Hy3Node::getFocusedNode(bool ignore_group_focus, bool stop_at_expanded) {
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window: return this;
|
|
case Hy3NodeType::Group:
|
|
if (this->data.as_group.focused_child == nullptr
|
|
|| (!ignore_group_focus && this->data.as_group.group_focused)
|
|
|| (stop_at_expanded && this->data.as_group.expand_focused != ExpandFocusType::NotExpanded))
|
|
{
|
|
return this;
|
|
} else {
|
|
return this->data.as_group.focused_child->getFocusedNode(
|
|
ignore_group_focus,
|
|
stop_at_expanded
|
|
);
|
|
}
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
|
|
bool Hy3Node::isIndirectlyFocused() {
|
|
Hy3Node* node = this;
|
|
|
|
while (node->parent != nullptr) {
|
|
if (!node->parent->data.as_group.group_focused
|
|
&& node->parent->data.as_group.focused_child != node)
|
|
return false;
|
|
node = node->parent;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// note: assumes this node is the expanded one without checking
|
|
Hy3Node& Hy3Node::getExpandActor() {
|
|
Hy3Node* node = this;
|
|
|
|
while (node->parent != nullptr
|
|
&& node->parent->data.as_group.expand_focused != ExpandFocusType::NotExpanded)
|
|
node = node->parent;
|
|
|
|
return *node;
|
|
}
|
|
|
|
void Hy3Node::recalcSizePosRecursive(bool no_animation) {
|
|
// clang-format off
|
|
static const auto gaps_in = ConfigValue<Hyprlang::CUSTOMTYPE, CCssGapData>("general:gaps_in");
|
|
static const auto gaps_out = ConfigValue<Hyprlang::CUSTOMTYPE, CCssGapData>("general:gaps_out");
|
|
static const auto group_inset = ConfigValue<Hyprlang::INT>("plugin:hy3:group_inset");
|
|
static const auto tab_bar_height = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:height");
|
|
static const auto tab_bar_padding = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:padding");
|
|
// clang-format on
|
|
|
|
// clang-format off
|
|
auto gap_topleft_offset = Vector2D(
|
|
-(gaps_in->left - gaps_out->left),
|
|
-(gaps_in->top - gaps_out->top)
|
|
);
|
|
|
|
auto gap_bottomright_offset = Vector2D(
|
|
-(gaps_in->right - gaps_out->right),
|
|
-(gaps_in->bottom - gaps_out->bottom)
|
|
);
|
|
// clang-format on
|
|
|
|
if (this->data.type == Hy3NodeType::Window && this->data.as_window->m_bIsFullscreen) {
|
|
auto* workspace = g_pCompositor->getWorkspaceByID(this->workspace_id);
|
|
auto* monitor = g_pCompositor->getMonitorFromID(workspace->m_iMonitorID);
|
|
|
|
if (workspace->m_efFullscreenMode == FULLSCREEN_FULL) {
|
|
this->data.as_window->m_vRealPosition = monitor->vecPosition;
|
|
this->data.as_window->m_vRealSize = monitor->vecSize;
|
|
return;
|
|
}
|
|
|
|
Hy3Node fake_node = {
|
|
.data = this->data.as_window,
|
|
.position = monitor->vecPosition + monitor->vecReservedTopLeft,
|
|
.size = monitor->vecSize - monitor->vecReservedTopLeft - monitor->vecReservedBottomRight,
|
|
.gap_topleft_offset = gap_topleft_offset,
|
|
.gap_bottomright_offset = gap_bottomright_offset,
|
|
.workspace_id = this->workspace_id,
|
|
};
|
|
|
|
this->layout->applyNodeDataToWindow(&fake_node);
|
|
return;
|
|
}
|
|
|
|
if (this->parent != nullptr) {
|
|
gap_topleft_offset = this->gap_topleft_offset;
|
|
gap_bottomright_offset = this->gap_bottomright_offset;
|
|
}
|
|
|
|
auto tpos = this->position;
|
|
auto tsize = this->size;
|
|
|
|
double tab_height_offset = *tab_bar_height + *tab_bar_padding;
|
|
|
|
if (this->data.type == Hy3NodeType::Window) {
|
|
this->data.as_window->setHidden(this->hidden);
|
|
this->layout->applyNodeDataToWindow(this, no_animation);
|
|
return;
|
|
}
|
|
|
|
auto* group = &this->data.as_group;
|
|
|
|
double constraint;
|
|
switch (group->layout) {
|
|
case Hy3GroupLayout::SplitH:
|
|
constraint = tsize.x - gap_topleft_offset.x - gap_bottomright_offset.x;
|
|
break;
|
|
case Hy3GroupLayout::SplitV:
|
|
constraint = tsize.y - gap_topleft_offset.y - gap_bottomright_offset.y;
|
|
break;
|
|
case Hy3GroupLayout::Tabbed: break;
|
|
}
|
|
|
|
auto expand_focused = group->expand_focused != ExpandFocusType::NotExpanded;
|
|
bool directly_contains_expanded =
|
|
expand_focused
|
|
&& (group->focused_child->data.type == Hy3NodeType::Window
|
|
|| group->focused_child->data.as_group.expand_focused == ExpandFocusType::NotExpanded);
|
|
|
|
auto child_count = group->children.size();
|
|
double ratio_mul =
|
|
group->layout != Hy3GroupLayout::Tabbed ? child_count <= 0 ? 0 : constraint / child_count : 0;
|
|
|
|
double offset = 0;
|
|
|
|
if (group->layout == Hy3GroupLayout::Tabbed && group->focused_child != nullptr
|
|
&& !group->focused_child->hidden)
|
|
{
|
|
group->focused_child->setHidden(false);
|
|
|
|
auto box = CBox {tpos.x, tpos.y, tsize.x, tsize.y};
|
|
g_pHyprRenderer->damageBox(&box);
|
|
}
|
|
|
|
if (group->expand_focused == ExpandFocusType::Latch) {
|
|
auto* expanded_node = group->focused_child;
|
|
|
|
while (expanded_node != nullptr && expanded_node->data.type == Hy3NodeType::Group
|
|
&& expanded_node->data.as_group.expand_focused != ExpandFocusType::NotExpanded)
|
|
{
|
|
expanded_node = expanded_node->data.as_group.focused_child;
|
|
}
|
|
|
|
if (expanded_node == nullptr) {
|
|
hy3_log(
|
|
ERR,
|
|
"recalcSizePosRecursive: unable to find expansion target of latch node {:x}",
|
|
(uintptr_t) this
|
|
);
|
|
errorNotif();
|
|
return;
|
|
}
|
|
|
|
expanded_node->position = tpos;
|
|
expanded_node->size = tsize;
|
|
expanded_node->setHidden(this->hidden);
|
|
|
|
expanded_node->gap_topleft_offset = gap_topleft_offset;
|
|
expanded_node->gap_bottomright_offset = gap_bottomright_offset;
|
|
|
|
expanded_node->recalcSizePosRecursive(no_animation);
|
|
}
|
|
|
|
for (auto* child: group->children) {
|
|
if (directly_contains_expanded && child == group->focused_child) {
|
|
switch (group->layout) {
|
|
case Hy3GroupLayout::SplitH: offset += child->size_ratio * ratio_mul; break;
|
|
case Hy3GroupLayout::SplitV: offset += child->size_ratio * ratio_mul; break;
|
|
case Hy3GroupLayout::Tabbed: break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
switch (group->layout) {
|
|
case Hy3GroupLayout::SplitH:
|
|
child->position.x = tpos.x + offset;
|
|
child->size.x = child->size_ratio * ratio_mul;
|
|
offset += child->size.x;
|
|
child->position.y = tpos.y;
|
|
child->size.y = tsize.y;
|
|
child->hidden = this->hidden || expand_focused;
|
|
|
|
if (group->children.size() == 1) {
|
|
child->gap_topleft_offset = gap_topleft_offset;
|
|
child->gap_bottomright_offset = gap_bottomright_offset;
|
|
child->size.x = tsize.x;
|
|
if (this->parent != nullptr) child->gap_bottomright_offset.x += *group_inset;
|
|
} else if (child == group->children.front()) {
|
|
child->gap_topleft_offset = gap_topleft_offset;
|
|
child->gap_bottomright_offset = Vector2D(0, gap_bottomright_offset.y);
|
|
child->size.x += gap_topleft_offset.x;
|
|
offset += gap_topleft_offset.x;
|
|
} else if (child == group->children.back()) {
|
|
child->gap_topleft_offset = Vector2D(0, gap_topleft_offset.y);
|
|
child->gap_bottomright_offset = gap_bottomright_offset;
|
|
child->size.x += gap_bottomright_offset.x;
|
|
} else {
|
|
child->gap_topleft_offset = Vector2D(0, gap_topleft_offset.y);
|
|
child->gap_bottomright_offset = Vector2D(0, gap_bottomright_offset.y);
|
|
}
|
|
|
|
child->recalcSizePosRecursive(no_animation);
|
|
break;
|
|
case Hy3GroupLayout::SplitV:
|
|
child->position.y = tpos.y + offset;
|
|
child->size.y = child->size_ratio * ratio_mul;
|
|
offset += child->size.y;
|
|
child->position.x = tpos.x;
|
|
child->size.x = tsize.x;
|
|
child->hidden = this->hidden || expand_focused;
|
|
|
|
if (group->children.size() == 1) {
|
|
child->gap_topleft_offset = gap_topleft_offset;
|
|
child->gap_bottomright_offset = gap_bottomright_offset;
|
|
child->size.y = tsize.y;
|
|
if (this->parent != nullptr) child->gap_bottomright_offset.y += *group_inset;
|
|
} else if (child == group->children.front()) {
|
|
child->gap_topleft_offset = gap_topleft_offset;
|
|
child->gap_bottomright_offset = Vector2D(gap_bottomright_offset.x, 0);
|
|
child->size.y += gap_topleft_offset.y;
|
|
offset += gap_topleft_offset.y;
|
|
} else if (child == group->children.back()) {
|
|
child->gap_topleft_offset = Vector2D(gap_topleft_offset.x, 0);
|
|
child->gap_bottomright_offset = gap_bottomright_offset;
|
|
child->size.y += gap_bottomright_offset.y;
|
|
} else {
|
|
child->gap_topleft_offset = Vector2D(gap_topleft_offset.x, 0);
|
|
child->gap_bottomright_offset = Vector2D(gap_bottomright_offset.x, 0);
|
|
}
|
|
|
|
child->recalcSizePosRecursive(no_animation);
|
|
break;
|
|
case Hy3GroupLayout::Tabbed:
|
|
child->position = tpos;
|
|
child->size = tsize;
|
|
child->hidden = this->hidden || expand_focused || group->focused_child != child;
|
|
|
|
child->gap_topleft_offset =
|
|
Vector2D(gap_topleft_offset.x, gap_topleft_offset.y + tab_height_offset);
|
|
child->gap_bottomright_offset = gap_bottomright_offset;
|
|
|
|
child->recalcSizePosRecursive(no_animation);
|
|
break;
|
|
}
|
|
}
|
|
|
|
this->updateTabBar(no_animation);
|
|
}
|
|
|
|
struct FindTopWindowInNodeResult {
|
|
CWindow* window = nullptr;
|
|
size_t index = 0;
|
|
};
|
|
|
|
void findTopWindowInNode(Hy3Node& node, FindTopWindowInNodeResult& result) {
|
|
switch (node.data.type) {
|
|
case Hy3NodeType::Window: {
|
|
auto* window = node.data.as_window;
|
|
auto& windows = g_pCompositor->m_vWindows;
|
|
|
|
for (; result.index < windows.size(); result.index++) {
|
|
if (&*windows[result.index] == window) {
|
|
result.window = window;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} break;
|
|
case Hy3NodeType::Group: {
|
|
auto& group = node.data.as_group;
|
|
|
|
if (group.layout == Hy3GroupLayout::Tabbed) {
|
|
if (group.focused_child != nullptr) findTopWindowInNode(*group.focused_child, result);
|
|
} else {
|
|
for (auto* child: group.children) {
|
|
findTopWindowInNode(*child, result);
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Hy3Node::updateTabBar(bool no_animation) {
|
|
if (this->data.type == Hy3NodeType::Group) {
|
|
auto& group = this->data.as_group;
|
|
|
|
if (group.layout == Hy3GroupLayout::Tabbed) {
|
|
if (group.tab_bar == nullptr) group.tab_bar = &this->layout->tab_groups.emplace_back(*this);
|
|
group.tab_bar->updateWithGroup(*this, no_animation);
|
|
|
|
FindTopWindowInNodeResult result;
|
|
findTopWindowInNode(*this, result);
|
|
group.tab_bar->target_window = result.window;
|
|
if (result.window != nullptr) group.tab_bar->workspace_id = result.window->m_iWorkspaceID;
|
|
} else if (group.tab_bar != nullptr) {
|
|
group.tab_bar->bar.beginDestroy();
|
|
group.tab_bar = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Hy3Node::updateTabBarRecursive() {
|
|
auto* node = this;
|
|
|
|
do {
|
|
node->updateTabBar();
|
|
node = node->parent;
|
|
} while (node != nullptr);
|
|
}
|
|
|
|
void Hy3Node::updateDecos() {
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window:
|
|
if (this->data.as_window->m_bIsMapped)
|
|
g_pCompositor->updateWindowAnimatedDecorationValues(this->data.as_window);
|
|
break;
|
|
case Hy3NodeType::Group:
|
|
for (auto* child: this->data.as_group.children) {
|
|
child->updateDecos();
|
|
}
|
|
|
|
this->updateTabBar();
|
|
}
|
|
}
|
|
|
|
std::string Hy3Node::getTitle() {
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window: return this->data.as_window->m_szTitle;
|
|
case Hy3NodeType::Group:
|
|
std::string title;
|
|
|
|
switch (this->data.as_group.layout) {
|
|
case Hy3GroupLayout::SplitH: title = "[H] "; break;
|
|
case Hy3GroupLayout::SplitV: title = "[V] "; break;
|
|
case Hy3GroupLayout::Tabbed: title = "[T] "; break;
|
|
}
|
|
|
|
if (this->data.as_group.focused_child == nullptr) {
|
|
title += "Group";
|
|
} else {
|
|
title += this->data.as_group.focused_child->getTitle();
|
|
}
|
|
|
|
return title;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
bool Hy3Node::isUrgent() {
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window: return this->data.as_window->m_bIsUrgent;
|
|
case Hy3NodeType::Group:
|
|
for (auto* child: this->data.as_group.children) {
|
|
if (child->isUrgent()) return true;
|
|
}
|
|
|
|
return false;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
void Hy3Node::setHidden(bool hidden) {
|
|
this->hidden = hidden;
|
|
|
|
if (this->data.type == Hy3NodeType::Group) {
|
|
for (auto* child: this->data.as_group.children) {
|
|
child->setHidden(hidden);
|
|
}
|
|
}
|
|
}
|
|
|
|
Hy3Node* Hy3Node::findNodeForTabGroup(Hy3TabGroup& tab_group) {
|
|
if (this->data.type == Hy3NodeType::Group) {
|
|
if (this->hidden) return nullptr;
|
|
|
|
auto& group = this->data.as_group;
|
|
|
|
if (group.layout == Hy3GroupLayout::Tabbed && group.tab_bar == &tab_group) {
|
|
return this;
|
|
}
|
|
|
|
for (auto& node: group.children) {
|
|
auto* r = node->findNodeForTabGroup(tab_group);
|
|
if (r != nullptr) return r;
|
|
}
|
|
} else return nullptr;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Hy3Node::appendAllWindows(std::vector<CWindow*>& list) {
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window: list.push_back(this->data.as_window); break;
|
|
case Hy3NodeType::Group:
|
|
for (auto* child: this->data.as_group.children) {
|
|
child->appendAllWindows(list);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::string Hy3Node::debugNode() {
|
|
std::stringstream buf;
|
|
std::string addr = "0x" + std::to_string((size_t) this);
|
|
switch (this->data.type) {
|
|
case Hy3NodeType::Window:
|
|
buf << "window(";
|
|
buf << std::hex << this;
|
|
buf << ") [hypr ";
|
|
buf << this->data.as_window;
|
|
buf << "] size ratio: ";
|
|
buf << this->size_ratio;
|
|
break;
|
|
case Hy3NodeType::Group:
|
|
buf << "group(";
|
|
buf << std::hex << this;
|
|
buf << ") [";
|
|
|
|
switch (this->data.as_group.layout) {
|
|
case Hy3GroupLayout::SplitH: buf << "splith"; break;
|
|
case Hy3GroupLayout::SplitV: buf << "splitv"; break;
|
|
case Hy3GroupLayout::Tabbed: buf << "tabs"; break;
|
|
}
|
|
|
|
buf << "] size ratio: ";
|
|
buf << this->size_ratio;
|
|
|
|
if (this->data.as_group.expand_focused != ExpandFocusType::NotExpanded) {
|
|
buf << ", has-expanded";
|
|
}
|
|
|
|
if (this->data.as_group.ephemeral) {
|
|
buf << ", ephemeral";
|
|
}
|
|
|
|
if (this->data.as_group.containment) {
|
|
buf << ", containment";
|
|
}
|
|
|
|
for (auto* child: this->data.as_group.children) {
|
|
buf << "\n|-";
|
|
if (child == nullptr) {
|
|
buf << "nullptr";
|
|
} else {
|
|
// this is terrible
|
|
for (char c: child->debugNode()) {
|
|
buf << c;
|
|
if (c == '\n') buf << " ";
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return buf.str();
|
|
}
|
|
|
|
Hy3Node* Hy3Node::removeFromParentRecursive(Hy3Node** expand_actor) {
|
|
Hy3Node* parent = this;
|
|
|
|
hy3_log(TRACE, "removing parent nodes of {:x} recursively", (uintptr_t) parent);
|
|
|
|
if (this->parent != nullptr) {
|
|
auto& actor = this->getExpandActor();
|
|
if (actor.data.type == Hy3NodeType::Group) {
|
|
actor.data.as_group.collapseExpansions();
|
|
if (expand_actor != nullptr) *expand_actor = &actor;
|
|
}
|
|
}
|
|
|
|
while (parent != nullptr) {
|
|
if (parent->parent == nullptr) {
|
|
if (parent != this) parent->layout->nodes.remove(*parent);
|
|
return nullptr;
|
|
}
|
|
|
|
auto* child = parent;
|
|
parent = parent->parent;
|
|
auto& group = parent->data.as_group;
|
|
|
|
if (group.children.size() > 2) {
|
|
auto iter = std::find(group.children.begin(), group.children.end(), child);
|
|
|
|
group.group_focused = false;
|
|
if (iter == group.children.begin()) {
|
|
group.focused_child = *std::next(iter);
|
|
} else {
|
|
group.focused_child = *std::prev(iter);
|
|
}
|
|
}
|
|
|
|
if (!group.children.remove(child)) {
|
|
hy3_log(
|
|
ERR,
|
|
"unable to remove child node {:x} from parent node {:x}, child's parent pointer is "
|
|
"likely dangling",
|
|
(uintptr_t) child,
|
|
(uintptr_t) parent
|
|
);
|
|
|
|
errorNotif();
|
|
return nullptr;
|
|
}
|
|
|
|
group.group_focused = false;
|
|
if (group.children.size() == 1) {
|
|
group.focused_child = group.children.front();
|
|
}
|
|
|
|
auto child_size_ratio = child->size_ratio;
|
|
if (child != this) {
|
|
parent->layout->nodes.remove(*child);
|
|
} else {
|
|
child->parent = nullptr;
|
|
}
|
|
|
|
if (!group.children.empty()) {
|
|
auto child_count = group.children.size();
|
|
if (std::find(group.children.begin(), group.children.end(), this) != group.children.end()) {
|
|
child_count -= 1;
|
|
}
|
|
|
|
auto splitmod = -((1.0 - child_size_ratio) / child_count);
|
|
|
|
for (auto* child: group.children) {
|
|
child->size_ratio += splitmod;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
this->parent = nullptr;
|
|
return parent;
|
|
}
|
|
|
|
Hy3Node* Hy3Node::intoGroup(Hy3GroupLayout layout, GroupEphemeralityOption ephemeral) {
|
|
this->layout->nodes.push_back({
|
|
.parent = this,
|
|
.data = layout,
|
|
.workspace_id = this->workspace_id,
|
|
.layout = this->layout,
|
|
});
|
|
|
|
auto* node = &this->layout->nodes.back();
|
|
swapData(*this, *node);
|
|
|
|
this->data = layout;
|
|
this->data.as_group.children.push_back(node);
|
|
this->data.as_group.group_focused = false;
|
|
this->data.as_group.focused_child = node;
|
|
this->data.as_group.ephemeral = ephemeral == GroupEphemeralityOption::Ephemeral
|
|
|| ephemeral == GroupEphemeralityOption::ForceEphemeral;
|
|
this->recalcSizePosRecursive();
|
|
this->updateTabBarRecursive();
|
|
|
|
return node;
|
|
}
|
|
|
|
bool Hy3Node::swallowGroups(Hy3Node* into) {
|
|
if (into == nullptr || into->data.type != Hy3NodeType::Group
|
|
|| into->data.as_group.children.size() != 1)
|
|
return false;
|
|
|
|
auto* child = into->data.as_group.children.front();
|
|
|
|
// a lot of segfaulting happens once the assumption that the root node is a
|
|
// group is wrong.
|
|
if (into->parent == nullptr && child->data.type != Hy3NodeType::Group) return false;
|
|
|
|
hy3_log(TRACE, "swallowing node {:x} into node {:x}", (uintptr_t) child, (uintptr_t) into);
|
|
|
|
Hy3Node::swapData(*into, *child);
|
|
into->layout->nodes.remove(*child);
|
|
|
|
return true;
|
|
}
|
|
|
|
Hy3Node* getOuterChild(Hy3GroupData& group, ShiftDirection direction) {
|
|
switch (direction) {
|
|
case ShiftDirection::Left:
|
|
case ShiftDirection::Up: return group.children.front(); break;
|
|
case ShiftDirection::Right:
|
|
case ShiftDirection::Down: return group.children.back(); break;
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
|
|
Hy3Node* Hy3Node::getImmediateSibling(ShiftDirection direction) {
|
|
const auto& group = this->parent->data.as_group;
|
|
|
|
auto iter = std::find(group.children.begin(), group.children.end(), this);
|
|
|
|
std::__cxx11::list<Hy3Node*>::const_iterator list_sibling;
|
|
|
|
switch (direction) {
|
|
case ShiftDirection::Left:
|
|
case ShiftDirection::Up: list_sibling = std::prev(iter); break;
|
|
case ShiftDirection::Right:
|
|
case ShiftDirection::Down: list_sibling = std::next(iter); break;
|
|
default: list_sibling = iter;
|
|
}
|
|
|
|
if (list_sibling == group.children.end()) {
|
|
hy3_log(WARN, "getImmediateSibling: sibling not found");
|
|
list_sibling = iter;
|
|
}
|
|
|
|
return *list_sibling;
|
|
}
|
|
|
|
Axis getAxis(Hy3GroupLayout layout) {
|
|
switch (layout) {
|
|
case Hy3GroupLayout::SplitH: return Axis::Horizontal;
|
|
case Hy3GroupLayout::SplitV: return Axis::Vertical;
|
|
default: return Axis::None;
|
|
}
|
|
}
|
|
|
|
Axis getAxis(ShiftDirection direction) {
|
|
switch (direction) {
|
|
case ShiftDirection::Left:
|
|
case ShiftDirection::Right: return Axis::Horizontal;
|
|
case ShiftDirection::Down:
|
|
case ShiftDirection::Up: return Axis::Vertical;
|
|
default: return Axis::None;
|
|
}
|
|
}
|
|
|
|
Hy3Node* Hy3Node::findNeighbor(ShiftDirection direction) {
|
|
auto current_node = this;
|
|
Hy3Node* sibling = nullptr;
|
|
|
|
while (sibling == nullptr && current_node->parent != nullptr) {
|
|
auto& parent_group = current_node->parent->data.as_group;
|
|
|
|
if (parent_group.layout != Hy3GroupLayout::Tabbed
|
|
&& getAxis(parent_group.layout) == getAxis(direction))
|
|
{
|
|
// If the current node is the outermost child of its parent group then proceed
|
|
// then we need to look at the parent - otherwise, the sibling is simply the immediate
|
|
// sibling in the child collection
|
|
if (getOuterChild(parent_group, direction) != current_node) {
|
|
sibling = current_node->getImmediateSibling(direction);
|
|
}
|
|
}
|
|
|
|
current_node = current_node->parent;
|
|
}
|
|
|
|
return sibling;
|
|
}
|
|
|
|
int directionToIteratorIncrement(ShiftDirection direction) {
|
|
switch (direction) {
|
|
case ShiftDirection::Left:
|
|
case ShiftDirection::Up: return -1;
|
|
case ShiftDirection::Right:
|
|
case ShiftDirection::Down: return 1;
|
|
default: hy3_log(WARN, "Unknown ShiftDirection enum value: {}", (int) direction); return 1;
|
|
}
|
|
}
|
|
|
|
void Hy3Node::resize(ShiftDirection direction, double delta, bool no_animation) {
|
|
auto& parent_node = this->parent;
|
|
auto& containing_group = parent_node->data.as_group;
|
|
|
|
if (containing_group.layout != Hy3GroupLayout::Tabbed
|
|
&& getAxis(direction) == getAxis(containing_group.layout))
|
|
{
|
|
double parent_size =
|
|
getAxis(direction) == Axis::Horizontal ? parent_node->size.x : parent_node->size.y;
|
|
auto ratio_mod = delta * (float) containing_group.children.size() / parent_size;
|
|
|
|
const auto end_of_children = containing_group.children.end();
|
|
auto iter = std::find(containing_group.children.begin(), end_of_children, this);
|
|
|
|
if (iter != end_of_children) {
|
|
const auto outermost_node_in_group = getOuterChild(containing_group, direction);
|
|
if (this != outermost_node_in_group) {
|
|
auto inc = directionToIteratorIncrement(direction);
|
|
iter = std::next(iter, inc);
|
|
ratio_mod *= inc;
|
|
}
|
|
|
|
if (iter != end_of_children) {
|
|
auto* neighbor = *iter;
|
|
auto requested_size_ratio = this->size_ratio + ratio_mod;
|
|
auto requested_neighbor_size_ratio = neighbor->size_ratio - ratio_mod;
|
|
|
|
if (requested_size_ratio >= MIN_RATIO && requested_neighbor_size_ratio >= MIN_RATIO) {
|
|
this->size_ratio = requested_size_ratio;
|
|
neighbor->size_ratio = requested_neighbor_size_ratio;
|
|
|
|
parent_node->recalcSizePosRecursive(no_animation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Hy3Node::swapData(Hy3Node& a, Hy3Node& b) {
|
|
Hy3NodeData aData = std::move(a.data);
|
|
a.data = std::move(b.data);
|
|
b.data = std::move(aData);
|
|
|
|
if (a.data.type == Hy3NodeType::Group) {
|
|
for (auto child: a.data.as_group.children) {
|
|
child->parent = &a;
|
|
}
|
|
}
|
|
|
|
if (b.data.type == Hy3NodeType::Group) {
|
|
for (auto child: b.data.as_group.children) {
|
|
child->parent = &b;
|
|
}
|
|
}
|
|
}
|