From 2fa77b4dbcdbb6921d47da7992770dfe6f03893b Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Wed, 9 Aug 2023 02:21:18 -0700 Subject: [PATCH] Initial work on expand/lens nodes --- README.md | 6 ++- src/Hy3Layout.cpp | 110 +++++++++++++++++++++++++++++++++++++++++--- src/Hy3Layout.hpp | 21 ++++++++- src/Hy3Node.cpp | 93 +++++++++++++++++++++++++++++++++---- src/Hy3Node.hpp | 11 ++++- src/dispatchers.cpp | 26 +++++++++++ 6 files changed, 249 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 7ab51b4..757bba2 100644 --- a/README.md +++ b/README.md @@ -238,5 +238,9 @@ plugin { - `prioritize_hovered` - prioritize the tab group under the mouse when multiple are stacked. use the lowest group if none is under the mouse. - `require_hovered` - affect the tab group under the mouse. do nothing if none are hovered. - `wrap` - wrap to the opposite size of the tab bar if moving off the end - - `hy3:setswallow, ` - set the containing node's window swallow state - `hy3:debugnodes` - print the node tree into the hyprland log + - :warning: **ALPHA QUALITY** `hy3:setswallow, ` - set the containing node's window swallow state + - :warning: **ALPHA QUALITY** `hy3:expand, ` - expand the current node to cover other nodes + - `expand` - expand by one node + - `shrink` - shrink by one node + - `base` - undo all expansions diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp index 57a6c3e..1c3ed1c 100644 --- a/src/Hy3Layout.cpp +++ b/src/Hy3Layout.cpp @@ -1059,6 +1059,91 @@ void Hy3Layout::killFocusedNode(int workspace) { } } +void Hy3Layout::expand(int workspace_id, ExpandOption option, ExpandFullscreenOption fs_option) { + auto* node = this->getWorkspaceFocusedNode(workspace_id, false, true); + if (node == nullptr) return; + + const auto workspace = g_pCompositor->getWorkspaceByID(workspace_id); + const auto monitor = g_pCompositor->getMonitorFromID(workspace->m_iMonitorID); + + switch (option) { + case ExpandOption::Expand: { + if (node->parent == nullptr) { + switch (fs_option) { + case ExpandFullscreenOption::MaximizeAsFullscreen: + case ExpandFullscreenOption::MaximizeIntermediate: goto fullscreen; + case ExpandFullscreenOption::MaximizeOnly: return; + } + } + + if (node->data.type == Hy3NodeType::Group) + node->data.as_group.expand_focused = ExpandFocusType::Stack; + + auto& group = node->parent->data.as_group; + group.focused_child = node; + group.expand_focused = ExpandFocusType::Latch; + + node->parent->recalcSizePosRecursive(); + + if (node->parent->parent == nullptr) { + switch (fs_option) { + case ExpandFullscreenOption::MaximizeAsFullscreen: goto fullscreen; + case ExpandFullscreenOption::MaximizeIntermediate: + case ExpandFullscreenOption::MaximizeOnly: return; + } + } + } break; + case ExpandOption::Shrink: + if (node->data.type == Hy3NodeType::Group) { + auto& group = node->data.as_group; + + group.expand_focused = ExpandFocusType::NotExpanded; + if (group.focused_child->data.type == Hy3NodeType::Group) + group.focused_child->data.as_group.expand_focused = ExpandFocusType::Latch; + + node->recalcSizePosRecursive(); + } + break; + case ExpandOption::Base: { + if (node->data.type == Hy3NodeType::Group) { + node->data.as_group.collapseExpansions(); + node->recalcSizePosRecursive(); + } + break; + } + case ExpandOption::Maximize: break; + case ExpandOption::Fullscreen: break; + } + + return; + + CWindow* window; +fullscreen: + if (node->data.type != Hy3NodeType::Window) return; + window = node->data.as_window; + if (!window->m_bIsFullscreen || g_pCompositor->isWorkspaceSpecial(window->m_iWorkspaceID)) return; + + if (workspace->m_bHasFullscreenWindow) return; + + window->m_bIsFullscreen = true; + workspace->m_bHasFullscreenWindow = true; + workspace->m_efFullscreenMode = FULLSCREEN_FULL; + window->m_vRealPosition = monitor->vecPosition; + window->m_vRealSize = monitor->vecSize; + goto fsupdate; +unfullscreen: + if (node->data.type != Hy3NodeType::Window) return; + window = node->data.as_window; + window->m_bIsFullscreen = false; + workspace->m_bHasFullscreenWindow = false; + goto fsupdate; +fsupdate: + g_pCompositor->updateWindowAnimatedDecorationValues(window); + g_pXWaylandManager->setWindowSize(window, window->m_vRealSize.goalv()); + g_pCompositor->moveWindowToTop(window); + this->recalculateMonitor(monitor->ID); +} + bool Hy3Layout::shouldRenderSelected(CWindow* window) { if (window == nullptr) return false; auto* root = this->getWorkspaceRootGroup(window->m_iWorkspaceID); @@ -1090,10 +1175,14 @@ Hy3Node* Hy3Layout::getWorkspaceRootGroup(const int& workspace) { return nullptr; } -Hy3Node* Hy3Layout::getWorkspaceFocusedNode(const int& workspace, bool ignore_group_focus) { +Hy3Node* Hy3Layout::getWorkspaceFocusedNode( + const int& workspace, + bool ignore_group_focus, + bool stop_at_expanded +) { auto* rootNode = this->getWorkspaceRootGroup(workspace); if (rootNode == nullptr) return nullptr; - return rootNode->getFocusedNode(ignore_group_focus); + return rootNode->getFocusedNode(ignore_group_focus, stop_at_expanded); } void Hy3Layout::renderHook(void*, std::any data) { @@ -1292,7 +1381,7 @@ Hy3Node* Hy3Layout::shiftOrGetFocus( bool once, bool visible ) { - auto* break_origin = &node; + auto* break_origin = &node.getExpandActor(); auto* break_parent = break_origin->parent; auto has_broken_once = false; @@ -1377,7 +1466,11 @@ Hy3Node* Hy3Layout::shiftOrGetFocus( if (shiftIsForward(direction)) iter = std::next(iter); else iter = std::prev(iter); - if ((*iter)->data.type == Hy3NodeType::Window || (shift && once && has_broken_once)) { + if ((*iter)->data.type == Hy3NodeType::Window + || ((*iter)->data.type == Hy3NodeType::Group + && (*iter)->data.as_group.expand_focused != ExpandFocusType::NotExpanded) + || (shift && once && has_broken_once)) + { if (shift) { if (target_group == node.parent) { if (shiftIsForward(direction)) insert = std::next(iter); @@ -1386,7 +1479,7 @@ Hy3Node* Hy3Layout::shiftOrGetFocus( if (shiftIsForward(direction)) insert = iter; else insert = std::next(iter); } - } else return *iter; + } else return (*iter)->getFocusedNode(); } else { // break into neighboring groups until we hit a window while (true) { @@ -1432,13 +1525,16 @@ Hy3Node* Hy3Layout::shiftOrGetFocus( break; } - if ((*iter)->data.type == Hy3NodeType::Window) { + if ((*iter)->data.type == Hy3NodeType::Window + || ((*iter)->data.type == Hy3NodeType::Group + && (*iter)->data.as_group.expand_focused != ExpandFocusType::NotExpanded)) + { if (shift) { if (shift_after) insert = std::next(iter); else insert = iter; break; } else { - return *iter; + return (*iter)->getFocusedNode(); } } } diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp index 62851fc..ee91b23 100644 --- a/src/Hy3Layout.hpp +++ b/src/Hy3Layout.hpp @@ -50,6 +50,20 @@ enum class SetSwallowOption { Toggle, }; +enum class ExpandOption { + Expand, + Shrink, + Base, + Maximize, + Fullscreen, +}; + +enum class ExpandFullscreenOption { + MaximizeOnly, + MaximizeIntermediate, + MaximizeAsFullscreen, +}; + class Hy3Layout: public IHyprLayout { public: virtual void onWindowCreated(CWindow*); @@ -84,11 +98,16 @@ public: void focusTab(int workspace, TabFocus target, TabFocusMousePriority, bool wrap_scroll, int index); void setNodeSwallow(int workspace, SetSwallowOption); void killFocusedNode(int workspace); + void expand(int workspace, ExpandOption, ExpandFullscreenOption); bool shouldRenderSelected(CWindow*); Hy3Node* getWorkspaceRootGroup(const int& workspace); - Hy3Node* getWorkspaceFocusedNode(const int& workspace, bool ignore_group_focus = false); + Hy3Node* getWorkspaceFocusedNode( + const int& workspace, + bool ignore_group_focus = false, + bool stop_at_expanded = false + ); static void renderHook(void*, std::any); static void windowGroupUrgentHook(void*, std::any); diff --git a/src/Hy3Node.cpp b/src/Hy3Node.cpp index 1b2c34b..0ce830a 100644 --- a/src/Hy3Node.cpp +++ b/src/Hy3Node.cpp @@ -12,6 +12,7 @@ Hy3GroupData::Hy3GroupData(Hy3GroupData&& from) { this->layout = from.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; @@ -35,6 +36,20 @@ bool Hy3GroupData::hasChild(Hy3Node* node) { 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; + } +} + // Hy3NodeData // Hy3NodeData::Hy3NodeData(): Hy3NodeData((CWindow*) nullptr) {} @@ -191,16 +206,29 @@ void Hy3Node::raiseToTop() { } } -Hy3Node* Hy3Node::getFocusedNode(bool ignore_group_focus) { +Hy3Node* Hy3Node::getFocusedNode(bool ignore_group_focus, bool stop_at_expanded) { switch (this->data.type) { case Hy3NodeType::Window: return this; case Hy3NodeType::Group: + Debug::log( + LOG, + "focusing %p, gf: %d, ef: %d (stop: %d)", + this, + this->data.as_group.group_focused, + this->data.as_group.expand_focused, + stop_at_expanded + ); + if (this->data.as_group.focused_child == nullptr - || (!ignore_group_focus && this->data.as_group.group_focused)) + || (!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); + return this->data.as_group.focused_child->getFocusedNode( + ignore_group_focus, + stop_at_expanded + ); } } } @@ -218,6 +246,17 @@ bool Hy3Node::isIndirectlyFocused() { 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 = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; @@ -260,8 +299,15 @@ void Hy3Node::recalcSizePosRecursive(bool no_animation) { 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() - (directly_contains_expanded ? 1 : 0); double ratio_mul = group->layout != Hy3GroupLayout::Tabbed - ? group->children.empty() ? 0 : constraint / group->children.size() + ? child_count <= 0 ? 0 : constraint / child_count : 0; double offset = 0; @@ -275,7 +321,27 @@ void Hy3Node::recalcSizePosRecursive(bool no_animation) { g_pHyprRenderer->damageBox(&box); } + // TODO: fix broken start positions when using hy3:expand, base + if (expand_focused) { + for (auto* child: group->children) { + if (child == group->focused_child) { + child->position = tpos; + child->size = tsize; + child->setHidden(hidden); + + child->gap_pos_offset = gap_pos_offset; + child->gap_size_offset = gap_size_offset; + } else { + child->setHidden(true); + } + + child->recalcSizePosRecursive(no_animation); + } + } + for (auto* child: group->children) { + if (expand_focused && child == group->focused_child) continue; + switch (group->layout) { case Hy3GroupLayout::SplitH: child->position.x = tpos.x + offset; @@ -283,7 +349,7 @@ void Hy3Node::recalcSizePosRecursive(bool no_animation) { offset += child->size.x; child->position.y = tpos.y; child->size.y = tsize.y; - child->setHidden(this->hidden); + child->setHidden(this->hidden || expand_focused); if (group->children.size() == 1) { child->gap_pos_offset = gap_pos_offset; @@ -311,7 +377,7 @@ void Hy3Node::recalcSizePosRecursive(bool no_animation) { offset += child->size.y; child->position.x = tpos.x; child->size.x = tsize.x; - child->setHidden(this->hidden); + child->setHidden(this->hidden || expand_focused); if (group->children.size() == 1) { child->gap_pos_offset = gap_pos_offset; @@ -336,8 +402,7 @@ void Hy3Node::recalcSizePosRecursive(bool no_animation) { case Hy3GroupLayout::Tabbed: child->position = tpos; child->size = tsize; - bool hidden = this->hidden || group->focused_child != child; - child->setHidden(hidden); + child->setHidden(this->hidden || expand_focused || group->focused_child != child); child->gap_pos_offset = Vector2D(gap_pos_offset.x, gap_pos_offset.y + tab_height_offset); child->gap_size_offset = Vector2D(gap_size_offset.x, gap_size_offset.y + tab_height_offset); @@ -528,6 +593,10 @@ std::string Hy3Node::debugNode() { 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"; } @@ -560,6 +629,14 @@ Hy3Node* Hy3Node::removeFromParentRecursive() { Debug::log(LOG, "Recursively removing parent nodes of %p", parent); + if (this->parent != nullptr) { + // note: may have missing recalcs causing weird animations. + auto& actor = this->getExpandActor(); + if (actor.data.type == Hy3NodeType::Group) { + actor.data.as_group.collapseExpansions(); + } + } + while (parent != nullptr) { if (parent->parent == nullptr) { Debug::log(ERR, "* UAF DEBUGGING - %p's parent is null, its the root group", parent); diff --git a/src/Hy3Node.hpp b/src/Hy3Node.hpp index 0b29099..29d84f0 100644 --- a/src/Hy3Node.hpp +++ b/src/Hy3Node.hpp @@ -21,11 +21,18 @@ enum class Hy3NodeType { Group, }; +enum class ExpandFocusType { + NotExpanded, + Latch, + Stack, +}; + struct Hy3GroupData { Hy3GroupLayout layout = Hy3GroupLayout::SplitH; std::list children; bool group_focused = true; Hy3Node* focused_child = nullptr; + ExpandFocusType expand_focused = ExpandFocusType::NotExpanded; bool ephemeral = false; bool containment = false; Hy3TabGroup* tab_bar = nullptr; @@ -34,6 +41,7 @@ struct Hy3GroupData { ~Hy3GroupData(); bool hasChild(Hy3Node* child); + void collapseExpansions(); private: Hy3GroupData(Hy3GroupData&&); @@ -84,8 +92,9 @@ struct Hy3Node { bool focusWindow(); void markFocused(); void raiseToTop(); - Hy3Node* getFocusedNode(bool ignore_group_focus = false); + Hy3Node* getFocusedNode(bool ignore_group_focus = false, bool stop_at_expanded = false); bool isIndirectlyFocused(); + Hy3Node& getExpandActor(); void recalcSizePosRecursive(bool no_animation = false); void updateTabBar(bool no_animation = false); diff --git a/src/dispatchers.cpp b/src/dispatchers.cpp index 6abb0ff..d6568e4 100644 --- a/src/dispatchers.cpp +++ b/src/dispatchers.cpp @@ -149,6 +149,31 @@ void dispatch_killactive(std::string value) { g_Hy3Layout->killFocusedNode(workspace); } +void dispatch_expand(std::string value) { + int workspace = workspace_for_action(); + if (workspace == -1) return; + + auto args = CVarList(value); + + ExpandOption expand; + ExpandFullscreenOption fs_expand = ExpandFullscreenOption::MaximizeIntermediate; + + if (args[0] == "expand") expand = ExpandOption::Expand; + else if (args[0] == "shrink") expand = ExpandOption::Shrink; + else if (args[0] == "base") expand = ExpandOption::Base; + else if (args[0] == "maximize") expand = ExpandOption::Maximize; + else if (args[0] == "fullscreen") expand = ExpandOption::Fullscreen; + else return; + + if (args[1] == "intermediate_maximize") fs_expand = ExpandFullscreenOption::MaximizeIntermediate; + else if (args[1] == "fullscreen_maximize") + fs_expand = ExpandFullscreenOption::MaximizeAsFullscreen; + else if (args[1] == "maximize_only") fs_expand = ExpandFullscreenOption::MaximizeOnly; + else if (args[1] != "") return; + + g_Hy3Layout->expand(workspace, expand, fs_expand); +} + void dispatch_debug(std::string arg) { int workspace = workspace_for_action(); if (workspace == -1) return; @@ -169,5 +194,6 @@ void registerDispatchers() { HyprlandAPI::addDispatcher(PHANDLE, "hy3:focustab", dispatch_focustab); HyprlandAPI::addDispatcher(PHANDLE, "hy3:setswallow", dispatch_setswallow); HyprlandAPI::addDispatcher(PHANDLE, "hy3:killactive", dispatch_killactive); + HyprlandAPI::addDispatcher(PHANDLE, "hy3:expand", dispatch_expand); HyprlandAPI::addDispatcher(PHANDLE, "hy3:debugnodes", dispatch_debug); }