diff --git a/README.md b/README.md index f698cb0..d53e4e2 100644 --- a/README.md +++ b/README.md @@ -91,9 +91,9 @@ plugin { ### Dispatcher list - `hy3:makegroup, ` - make a vertical / horizontal split or tab group - - `hy3:movefocus, [, visible]` - move the focus left, up, down, or right + - `hy3:movefocus, , [visible]` - move the focus left, up, down, or right - `visible` - only move between visible nodes, not hidden tabs - - `hy3:movewindow, [, once]` - move a window left, up, down, or right + - `hy3:movewindow, , [once]` - move a window left, up, down, or right - `once` - only move directly to the neighboring group, without moving into any of its subgroups - `hy3:changefocus, ` - `top` - focus all nodes in the workspace @@ -102,6 +102,16 @@ plugin { - `lower` - lower focus one level - `tab` - raise focus to the nearest tab - `tabnode` - raise focus to the nearest node under the tab + - `hy3:focustab ` + - `mouse` - focus the tab under the mouse, works well with a non consuming bind, e.g. + ```conf + # binds hy3:focustab to lmb and still allows windows to receive clicks + bindn = , mouse:272, hy3:focustab, mouse + ``` + - `l | r | left | right` - direction to change tabs in + - `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:debugnodes` - print the node tree into the hyprland log ## Installing diff --git a/flake.nix b/flake.nix index 4eac23c..34c543b 100644 --- a/flake.nix +++ b/flake.nix @@ -41,7 +41,7 @@ name = "hy3"; nativeBuildInputs = with pkgs; [ - clang-tools + clang-tools_16 bear ]; diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp index 6d2bcf2..4490844 100644 --- a/src/Hy3Layout.cpp +++ b/src/Hy3Layout.cpp @@ -196,10 +196,12 @@ void Hy3Node::recalcSizePosRecursive(bool force) { double offset = 0; - if (group->layout == Hy3GroupLayout::Tabbed && group->focused_child != nullptr && !group->focused_child->hidden) { + if (group->layout == Hy3GroupLayout::Tabbed && group->focused_child != nullptr + && !group->focused_child->hidden) + { group->focused_child->setHidden(false); - auto box = wlr_box { tpos.x, tpos.y, tsize.x, tsize.y }; + auto box = wlr_box {tpos.x, tpos.y, tsize.x, tsize.y}; g_pHyprRenderer->damageBox(&box); } @@ -1684,6 +1686,8 @@ bottom: Hy3Node* Hy3Node::findNodeForTabGroup(Hy3TabGroup& tab_group) { if (this->data.type == Hy3NodeData::Group) { + if (this->hidden) return nullptr; + auto& group = this->data.as_group; if (group.layout == Hy3GroupLayout::Tabbed && group.tab_bar == &tab_group) { @@ -1699,50 +1703,105 @@ Hy3Node* Hy3Node::findNodeForTabGroup(Hy3TabGroup& tab_group) { return nullptr; } -void Hy3Layout::focusTab(int workspace) { +void Hy3Layout::focusTab( + int workspace, + TabFocus target, + TabFocusMousePriority mouse, + bool wrap_scroll +) { + static const auto* tab_bar_padding + = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; + auto* node = this->getWorkspaceRootGroup(workspace); if (node == nullptr) return; - auto mouse_pos = g_pInputManager->getMouseCoordsInternal(); + Hy3Node* tab_node = nullptr; + Hy3Node* tab_focused_node; - for (auto& tab_group: this->tab_groups) { - auto pos = tab_group.pos.vec(); - if (pos.x > mouse_pos.x || pos.y > mouse_pos.y) continue; - auto size = tab_group.size.vec(); - if (pos.x + size.x < mouse_pos.x || pos.y + size.y < mouse_pos.y) continue; + if (target == TabFocus::MouseLocation || mouse != TabFocusMousePriority::Ignore) { + auto mouse_pos = g_pInputManager->getMouseCoordsInternal(); - Debug::log( - LOG, - "!!! tab group clicked: %f %f, %f %f [%f %f]", - pos.x, - pos.y, - size.x, - size.y, - mouse_pos.x, - mouse_pos.y - ); + for (auto& tab_group: this->tab_groups) { + auto pos = tab_group.pos.vec(); + if (pos.x > mouse_pos.x || pos.y > mouse_pos.y) continue; + auto size = tab_group.size.vec(); + if (pos.x + size.x < mouse_pos.x || pos.y + size.y + *tab_bar_padding < mouse_pos.y) continue; - auto* group = node->findNodeForTabGroup(tab_group); - if (group == nullptr) continue; + tab_node = node->findNodeForTabGroup(tab_group); + if (tab_node == nullptr) continue; + if (target != TabFocus::MouseLocation) goto hastab; - auto delta = mouse_pos - pos; + auto delta = mouse_pos - pos; - auto& node_list = group->data.as_group.children; - auto node_iter = node_list.begin(); + auto& node_list = tab_node->data.as_group.children; + auto node_iter = node_list.begin(); - for (auto& tab: tab_group.bar.entries) { - if (node_iter == node_list.end()) break; + for (auto& tab: tab_group.bar.entries) { + if (node_iter == node_list.end()) break; - if (delta.x > tab.offset.fl() * size.x - && delta.x < (tab.offset.fl() + tab.width.fl()) * size.x) - { - (*node_iter)->focus(); - break; + if (delta.x > tab.offset.fl() * size.x + && delta.x < (tab.offset.fl() + tab.width.fl()) * size.x) + { + tab_focused_node = *node_iter; + goto hastab; + } + + node_iter = std::next(node_iter); } - node_iter = std::next(node_iter); + node->recalcSizePosRecursive(); + } + + if (target == TabFocus::MouseLocation || mouse == TabFocusMousePriority::Require) return; + } + + if (tab_node == nullptr) { + tab_node = this->getWorkspaceFocusedNode(workspace); + if (tab_node == nullptr) return; + + while (tab_node != nullptr && tab_node->data.as_group.layout != Hy3GroupLayout::Tabbed + && tab_node->parent != nullptr) + tab_node = tab_node->parent; + + if (tab_node == nullptr || tab_node->data.type != Hy3NodeData::Group + || tab_node->data.as_group.layout != Hy3GroupLayout::Tabbed) + return; + } + +hastab: + if (target != TabFocus::MouseLocation) { + if (tab_node->data.as_group.focused_child == nullptr + || tab_node->data.as_group.children.size() < 2) + return; + + auto& children = tab_node->data.as_group.children; + auto node_iter + = std::find(children.begin(), children.end(), tab_node->data.as_group.focused_child); + if (node_iter == children.end()) return; + if (target == TabFocus::Left) { + if (node_iter == children.begin()) { + if (wrap_scroll) node_iter = std::prev(children.end()); + else return; + } else node_iter = std::prev(node_iter); + + tab_focused_node = *node_iter; + } else { + if (node_iter == std::prev(children.end())) { + if (wrap_scroll) node_iter = children.begin(); + else return; + } else node_iter = std::next(node_iter); + + tab_focused_node = *node_iter; } } + + auto* focus = tab_focused_node; + while (focus->data.type == Hy3NodeData::Group && !focus->data.as_group.group_focused + && focus->data.as_group.focused_child != nullptr) + focus = focus->data.as_group.focused_child; + + focus->focus(); + tab_node->recalcSizePosRecursive(); } bool Hy3Layout::shouldRenderSelected(CWindow* window) { diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp index c534e5a..39f6b6f 100644 --- a/src/Hy3Layout.hpp +++ b/src/Hy3Layout.hpp @@ -31,6 +31,18 @@ enum class FocusShift { TabNode, }; +enum class TabFocus { + MouseLocation, + Left, + Right, +}; + +enum class TabFocusMousePriority { + Ignore, + Prioritize, + Require, +}; + struct Hy3GroupData { Hy3GroupLayout layout = Hy3GroupLayout::SplitH; std::list children; @@ -146,7 +158,7 @@ public: void shiftWindow(int, ShiftDirection, bool); void shiftFocus(int, ShiftDirection, bool); void changeFocus(int, FocusShift); - void focusTab(int); + void focusTab(int, TabFocus, TabFocusMousePriority, bool); bool shouldRenderSelected(CWindow*); diff --git a/src/main.cpp b/src/main.cpp index 05daae3..efc0040 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,11 +93,37 @@ void dispatch_changefocus(std::string arg) { else if (arg == "tabnode") g_Hy3Layout->changeFocus(workspace, FocusShift::TabNode); } -void dispatch_focustab(std::string arg) { +void dispatch_focustab(std::string value) { int workspace = workspace_for_action(); if (workspace == -1) return; - g_Hy3Layout->focusTab(workspace); + auto i = 0; + auto args = CVarList(value); + + TabFocus focus; + auto mouse = TabFocusMousePriority::Ignore; + bool wrap_scroll = false; + + if (args[i] == "l" || args[i] == "left") focus = TabFocus::Left; + else if (args[i] == "r" || args[i] == "right") focus = TabFocus::Right; + else if (args[i] == "mouse") { + g_Hy3Layout->focusTab(workspace, TabFocus::MouseLocation, mouse, false); + return; + } else return; + + i++; + + if (args[i] == "prioritize_hovered") { + mouse = TabFocusMousePriority::Prioritize; + i++; + } else if (args[i] == "require_hovered") { + mouse = TabFocusMousePriority::Require; + i++; + } + + if (args[i++] == "wrap") wrap_scroll = true; + + g_Hy3Layout->focusTab(workspace, focus, mouse, wrap_scroll); } void dispatch_debug(std::string arg) {