diff --git a/.gitignore b/.gitignore index b159eae..36b6e89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ build/ -compile_commands.json \ No newline at end of file +compile_commands.json +.vscode/ +*.log diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c97a0bf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +## Upcoming + +- Implement `resizeactivewindow` for floating windows +- Fully implement `resizeactivewindow` for tiled windows + +## hl0.35.0 and before + +- Fixed `hy3:killactive` and `hy3:movetoworkspace` not working in fullscreen. +- `hy3:movetoworkspace` added to move a whole node to a workspace. +- Newly tiled windows (usually from moving a window to a new workspace) are now +placed relative to the last selected node. + +## hl0.34.0 and before +*check commit history* diff --git a/README.md b/README.md index 1f3d69c..d74e8d9 100755 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ i3 / sway like layout for [hyprland](https://github.com/hyprwm/hyprland). [Installation](#installation), [Configuration](#configuration) +*Check the [changelog](./CHANGELOG.md) for a list of new features and improvements* + ### Features - [x] i3 like tiling - [x] Node based window manipulation (you can interact with multiple windows at once) @@ -27,7 +29,7 @@ Commits are tested before pushing and will build against the hyprland release ** There may be a mismatch with hyprland's main branch. If hy3 fails to build against hyprland's main branch please make an issue or ping me in the [hy3 matrix room](https://matrix.to/#/#hy3-support:outfoxxed.me). -Tagged hy3 versions are always checked against the corrosponding hyprland tag. +Tagged hy3 versions are always checked against the corresponding hyprland tag. If you encounter any bugs, please report them in the issue tracker. @@ -120,7 +122,7 @@ isn't capable of locking hy3 builds to the correct hyprland version. > exec-once = hyprpm reload -n > ``` > -> in your hyprland.conf. (See [the wiki](https://wiki.hyprland.org/Plugins/Using-Plugins/) for details.) +> in your hyprland.conf. (See [the wiki](https://wiki.hyprland.org/Plugins/Using-Plugins/) for details.) To install hy3 via hyprpm run @@ -295,7 +297,7 @@ plugin { # 0 = always automatically split horizontally # = pixel height to split at trigger_height = # default: 0 - + # a space or comma separated list of workspace ids where autotile should be enabled # it's possible to create an exception rule by prefixing the definition with "not:" # workspaces = 1,2 # autotiling will only be enabled on workspaces 1 and 2 @@ -320,6 +322,8 @@ plugin { - `hy3:movewindow, , [once], [visible]` - move a window left, up, down, or right - `once` - only move directly to the neighboring group, without moving into any of its subgroups - `visible` - only move between visible nodes, not hidden tabs + - `hy3:movetoworkspace, , [follow]` - move the active node to the given workspace + - `follow` - change focus to the given workspace when moving the selected node - `hy3:killactive` - close all windows in the focused node - `hy3:changefocus, ` - `top` - focus all nodes in the workspace diff --git a/flake.lock b/flake.lock index d7a752d..0abb1d4 100755 --- a/flake.lock +++ b/flake.lock @@ -3,17 +3,18 @@ "hyprland": { "inputs": { "hyprland-protocols": "hyprland-protocols", + "hyprlang": "hyprlang", "nixpkgs": "nixpkgs", "systems": "systems", "wlroots": "wlroots", "xdph": "xdph" }, "locked": { - "lastModified": 1702236723, - "narHash": "sha256-zIEnimM1vhsFkz+Kubb8kJ6YgHuLe56pALOSJc6CMVY=", + "lastModified": 1708650152, + "narHash": "sha256-OZUS5FED7KKAPpNaJYQr4BPGXQzGrDFgkKVg9U2aZh8=", "owner": "hyprwm", "repo": "Hyprland", - "rev": "167f2ed3b2bb18ceeabb831ac80b655ef8e16867", + "rev": "8c3613632a6ccebf9fb797ec756ecfce99514eec", "type": "github" }, "original": { @@ -47,13 +48,56 @@ "type": "github" } }, + "hyprlang": { + "inputs": { + "nixpkgs": [ + "hyprland", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1708005943, + "narHash": "sha256-9TT3xk++LI5/SPYgjYX34xZ4ebR93c1uerIq+SE/ues=", + "owner": "hyprwm", + "repo": "hyprlang", + "rev": "aeb3e012adc7b3235335c540b214b82267c2b983", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprlang", + "type": "github" + } + }, + "hyprlang_2": { + "inputs": { + "nixpkgs": [ + "hyprland", + "xdph", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1704287638, + "narHash": "sha256-TuRXJGwtK440AXQNl5eiqmQqY4LZ/9+z/R7xC0ie3iA=", + "owner": "hyprwm", + "repo": "hyprlang", + "rev": "6624f2bb66d4d27975766e81f77174adbe58ec97", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprlang", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1700612854, - "narHash": "sha256-yrQ8osMD+vDLGFX7pcwsY/Qr5PUd6OmDMYJZzZi0+zc=", + "lastModified": 1707546158, + "narHash": "sha256-nYYJTpzfPMDxI8mzhQsYjIUX+grorqjKEU9Np6Xwy/0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "19cbff58383a4ae384dea4d1d0c823d72b49d614", + "rev": "d934204a0f8d9198e1e4515dd6fec76a139c87f0", "type": "github" }, "original": { @@ -87,18 +131,18 @@ "flake": false, "locked": { "host": "gitlab.freedesktop.org", - "lastModified": 1701368958, - "narHash": "sha256-7kvyoA91etzVEl9mkA/EJfB6z/PltxX7Xc4gcr7/xlo=", + "lastModified": 1708558866, + "narHash": "sha256-Mz6hCtommq7RQfcPnxLINigO4RYSNt23HeJHC6mVmWI=", "owner": "wlroots", "repo": "wlroots", - "rev": "5d639394f3e83b01596dcd166a44a9a1a2583350", + "rev": "0cb091f1a2d345f37d2ee445f4ffd04f7f4ec9e5", "type": "gitlab" }, "original": { "host": "gitlab.freedesktop.org", "owner": "wlroots", "repo": "wlroots", - "rev": "5d639394f3e83b01596dcd166a44a9a1a2583350", + "rev": "0cb091f1a2d345f37d2ee445f4ffd04f7f4ec9e5", "type": "gitlab" } }, @@ -108,6 +152,7 @@ "hyprland", "hyprland-protocols" ], + "hyprlang": "hyprlang_2", "nixpkgs": [ "hyprland", "nixpkgs" @@ -118,11 +163,11 @@ ] }, "locked": { - "lastModified": 1700508250, - "narHash": "sha256-X4o/mifI7Nhu0UKYlxx53wIC+gYDo3pVM9L2u3PE2bE=", + "lastModified": 1706521509, + "narHash": "sha256-AInZ50acOJ3wzUwGzNr1TmxGTMx+8j6oSTzz4E7Vbp8=", "owner": "hyprwm", "repo": "xdg-desktop-portal-hyprland", - "rev": "eb120ff25265ecacd0fc13d7dab12131b60d0f47", + "rev": "c06fd88b3da492b8f9067be021b9184f7012b5a8", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 3be3c6c..570b3f6 100755 --- a/flake.nix +++ b/flake.nix @@ -10,7 +10,7 @@ packages = hyprlandSystems (system: pkgs: let hyprlandPackage = hyprland.packages.${system}.hyprland; in rec { - hy3 = hyprlandPackage.stdenv.mkDerivation { + hy3 = (pkgs.keepDebugInfo hyprlandPackage.stdenv).mkDerivation { pname = "hy3"; version = "0.1"; src = ./.; @@ -45,7 +45,7 @@ name = "hy3"; nativeBuildInputs = with pkgs; [ - clang-tools_16 + clang-tools_17 bear ]; diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp index 8211a97..a4f28a7 100644 --- a/src/Hy3Layout.cpp +++ b/src/Hy3Layout.cpp @@ -73,11 +73,14 @@ void Hy3Layout::onWindowCreated(CWindow* window, eDirection direction) { void Hy3Layout::onWindowCreatedTiling(CWindow* window, eDirection) { hy3_log( - TRACE, - "onWindowCreatedTiling called with window {:x} (floating: {})", + LOG, + "onWindowCreatedTiling called with window {:x} (floating: {}, monitor: {}, workspace: {})", (uintptr_t) window, - window->m_bIsFloating + window->m_bIsFloating, + window->m_iMonitorID, + window->m_iWorkspaceID ); + if (window->m_bIsFloating) return; auto* existing = this->getNodeFromWindow(window); @@ -91,55 +94,104 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window, eDirection) { return; } - auto* monitor = g_pCompositor->getMonitorFromID(window->m_iMonitorID); + this->nodes.push_back({ + .parent = nullptr, + .data = window, + .workspace_id = window->m_iWorkspaceID, + .layout = this, + }); + + this->insertNode(this->nodes.back()); +} + +void Hy3Layout::insertNode(Hy3Node& node) { + if (node.parent != nullptr) { + hy3_log( + ERR, + "insertNode called for node {:x} which already has a parent ({:x})", + (uintptr_t) &node, + (uintptr_t) node.parent + ); + return; + } + + auto* workspace = g_pCompositor->getWorkspaceByID(node.workspace_id); + + if (workspace == nullptr) { + hy3_log( + ERR, + "insertNode called for node {:x} with invalid workspace id {}", + (uintptr_t) &node, + node.workspace_id + ); + return; + } + + node.reparenting = true; + + auto* monitor = g_pCompositor->getMonitorFromID(workspace->m_iMonitorID); Hy3Node* opening_into; Hy3Node* opening_after = nullptr; - if (monitor->activeWorkspace != -1) { - auto* root = this->getWorkspaceRootGroup(monitor->activeWorkspace); + auto* root = this->getWorkspaceRootGroup(node.workspace_id); - if (root != nullptr) { - opening_after = root->getFocusedNode(); + if (root != nullptr) { + opening_after = root->getFocusedNode(); - // opening_after->parent cannot be nullptr - if (opening_after == root) { - opening_after = - opening_after->intoGroup(Hy3GroupLayout::SplitH, GroupEphemeralityOption::Standard); - } + // opening_after->parent cannot be nullptr + if (opening_after == root) { + opening_after = + opening_after->intoGroup(Hy3GroupLayout::SplitH, GroupEphemeralityOption::Standard); } } if (opening_after == nullptr) { - if (g_pCompositor->m_pLastWindow != nullptr && !g_pCompositor->m_pLastWindow->m_bIsFloating - && g_pCompositor->m_pLastWindow != window - && g_pCompositor->m_pLastWindow->m_iWorkspaceID == window->m_iWorkspaceID + if (g_pCompositor->m_pLastWindow != nullptr + && g_pCompositor->m_pLastWindow->m_iWorkspaceID == node.workspace_id + && !g_pCompositor->m_pLastWindow->m_bIsFloating + && (node.data.type == Hy3NodeType::Window + || g_pCompositor->m_pLastWindow != node.data.as_window) && g_pCompositor->m_pLastWindow->m_bIsMapped) { opening_after = this->getNodeFromWindow(g_pCompositor->m_pLastWindow); } else { - opening_after = this->getNodeFromWindow( - g_pCompositor->vectorToWindowTiled(g_pInputManager->getMouseCoordsInternal()) + auto* mouse_window = g_pCompositor->vectorToWindowUnified( + g_pInputManager->getMouseCoordsInternal(), + RESERVED_EXTENTS | INPUT_EXTENTS ); + + if (mouse_window != nullptr && mouse_window->m_iWorkspaceID == node.workspace_id) { + opening_after = this->getNodeFromWindow(mouse_window); + } } } - if (opening_after != nullptr && opening_after->workspace_id != window->m_iWorkspaceID) { + if (opening_after != nullptr + && ((node.data.type == Hy3NodeType::Group + && (opening_after == &node || node.data.as_group.hasChild(opening_after))) + || opening_after->reparenting)) + { opening_after = nullptr; } if (opening_after != nullptr) { opening_into = opening_after->parent; } else { - if ((opening_into = this->getWorkspaceRootGroup(window->m_iWorkspaceID)) == nullptr) { - static const auto* tab_first_window = - &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tab_first_window")->intValue; + if ((opening_into = this->getWorkspaceRootGroup(node.workspace_id)) == nullptr) { + static const auto tab_first_window = + ConfigValue("plugin:hy3:tab_first_window"); + + auto width = + monitor->vecSize.x - monitor->vecReservedBottomRight.x - monitor->vecReservedTopLeft.x; + auto height = + monitor->vecSize.y - monitor->vecReservedBottomRight.y - monitor->vecReservedTopLeft.y; this->nodes.push_back({ - .data = Hy3GroupLayout::SplitH, + .data = height > width ? Hy3GroupLayout::SplitV : Hy3GroupLayout::SplitH, .position = monitor->vecPosition + monitor->vecReservedTopLeft, .size = monitor->vecSize - monitor->vecReservedTopLeft - monitor->vecReservedBottomRight, - .workspace_id = window->m_iWorkspaceID, + .workspace_id = node.workspace_id, .layout = this, }); @@ -151,7 +203,7 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window, eDirection) { .data = Hy3GroupLayout::Tabbed, .position = parent.position, .size = parent.size, - .workspace_id = window->m_iWorkspaceID, + .workspace_id = node.workspace_id, .layout = this, }); @@ -168,23 +220,23 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window, eDirection) { return; } - if (opening_into->workspace_id != window->m_iWorkspaceID) { + if (opening_into->workspace_id != node.workspace_id) { hy3_log( WARN, "opening_into node ({:x}) is on workspace {} which does not match the new window " "(workspace {})", (uintptr_t) opening_into, opening_into->workspace_id, - window->m_iWorkspaceID + node.workspace_id ); } { // clang-format off - static const auto* at_enable = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:autotile:enable")->intValue; - static const auto* at_ephemeral = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:autotile:ephemeral_groups")->intValue; - static const auto* at_trigger_width = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:autotile:trigger_width")->intValue; - static const auto* at_trigger_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:autotile:trigger_height")->intValue; + static const auto at_enable = ConfigValue("plugin:hy3:autotile:enable"); + static const auto at_ephemeral = ConfigValue("plugin:hy3:autotile:ephemeral_groups"); + static const auto at_trigger_width = ConfigValue("plugin:hy3:autotile:trigger_width"); + static const auto at_trigger_height = ConfigValue("plugin:hy3:autotile:trigger_height"); // clang-format on this->updateAutotileWorkspaces(); @@ -210,14 +262,8 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window, eDirection) { } } - this->nodes.push_back({ - .parent = opening_into, - .data = window, - .workspace_id = window->m_iWorkspaceID, - .layout = this, - }); - - auto& node = this->nodes.back(); + node.parent = opening_into; + node.reparenting = false; if (opening_after == nullptr) { opening_into->data.as_group.children.push_back(&node); @@ -230,8 +276,7 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window, eDirection) { hy3_log( LOG, - "tiled window ({:x} as node {:x}) after node {:x} in node {:x}", - (uintptr_t) window, + "tiled node {:x} inserted after node {:x} in node {:x}", (uintptr_t) &node, (uintptr_t) opening_after, (uintptr_t) opening_into @@ -242,8 +287,8 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window, eDirection) { } void Hy3Layout::onWindowRemovedTiling(CWindow* window) { - static const auto* node_collapse_policy = - &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:node_collapse_policy")->intValue; + static const auto node_collapse_policy = + ConfigValue("plugin:hy3:node_collapse_policy"); auto* node = this->getNodeFromWindow(window); @@ -352,215 +397,100 @@ void Hy3Layout::recalculateWindow(CWindow* window) { node->recalcSizePosRecursive(); } +ShiftDirection reverse(ShiftDirection direction) { + switch (direction) { + case ShiftDirection::Left: return ShiftDirection::Right; + case ShiftDirection::Right: return ShiftDirection::Left; + case ShiftDirection::Up: return ShiftDirection::Down; + case ShiftDirection::Down: return ShiftDirection::Up; + default: return direction; + } +} + void Hy3Layout::resizeActiveWindow(const Vector2D& delta, eRectCorner corner, CWindow* pWindow) { auto window = pWindow ? pWindow : g_pCompositor->m_pLastWindow; if (!g_pCompositor->windowValidMapped(window)) return; auto* node = this->getNodeFromWindow(window); - if (node == nullptr) return; - if (node->parent != nullptr && node->parent->data.as_group.focused_child == node) + + if (node != nullptr) { node = &node->getExpandActor(); - bool drag_x; - bool drag_y; + auto monitor = g_pCompositor->getMonitorFromID(window->m_iMonitorID); - if (corner == CORNER_NONE) { - drag_x = delta.x > 0; - drag_y = delta.y > 0; - } else { - drag_x = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT; - drag_y = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT; - } + const bool display_left = + STICKS(node->position.x, monitor->vecPosition.x + monitor->vecReservedTopLeft.x); + const bool display_right = STICKS( + node->position.x + node->size.x, + monitor->vecPosition.x + monitor->vecSize.x - monitor->vecReservedBottomRight.x + ); + const bool display_top = + STICKS(node->position.y, monitor->vecPosition.y + monitor->vecReservedTopLeft.y); + const bool display_bottom = STICKS( + node->position.y + node->size.y, + monitor->vecPosition.y + monitor->vecSize.y - monitor->vecReservedBottomRight.y + ); - const auto animate = - &g_pConfigManager->getConfigValuePtr("misc:animate_manual_resizes")->intValue; + Vector2D resize_delta = delta; + bool node_is_root = (node->data.type == Hy3NodeType::Group && node->parent == nullptr) + || (node->data.type == Hy3NodeType::Window + && (node->parent == nullptr || node->parent->parent == nullptr)); - auto monitor = g_pCompositor->getMonitorFromID(window->m_iMonitorID); - - const bool display_left = - STICKS(node->position.x, monitor->vecPosition.x + monitor->vecReservedTopLeft.x); - const bool display_right = STICKS( - node->position.x + node->size.x, - monitor->vecPosition.x + monitor->vecSize.x - monitor->vecReservedBottomRight.x - ); - const bool display_top = - STICKS(node->position.y, monitor->vecPosition.y + monitor->vecReservedTopLeft.y); - const bool display_bottom = STICKS( - node->position.y + node->size.y, - monitor->vecPosition.y + monitor->vecSize.y - monitor->vecReservedBottomRight.y - ); - - Vector2D allowed_movement = delta; - if (display_left && display_right) allowed_movement.x = 0; - if (display_top && display_bottom) allowed_movement.y = 0; - - auto* inner_node = node; - - // break into parent groups when encountering a corner we're dragging in or a - // tab group - while (inner_node->parent != nullptr) { - auto& group = inner_node->parent->data.as_group; - - switch (group.layout) { - case Hy3GroupLayout::Tabbed: - // treat tabbed layouts as if they dont exist during resizing - goto cont; - case Hy3GroupLayout::SplitH: - if ((drag_x && group.children.back() == inner_node) - || (!drag_x && group.children.front() == inner_node)) - { - goto cont; - } - break; - case Hy3GroupLayout::SplitV: - if ((drag_y && group.children.back() == inner_node) - || (!drag_y && group.children.front() == inner_node)) - { - goto cont; - } - break; + if (node_is_root) { + if (display_left && display_right) resize_delta.x = 0; + if (display_top && display_bottom) resize_delta.y = 0; } - break; - cont: - inner_node = inner_node->parent; - } + // Don't execute the logic unless there's something to do + if (resize_delta.x != 0 || resize_delta.y != 0) { + ShiftDirection target_edge_x; + ShiftDirection target_edge_y; - auto* inner_parent = inner_node->parent; - if (inner_parent == nullptr) return; + // Determine the direction in which we're going to look for the neighbor node + // that will be resized + if (corner == CORNER_NONE) { // It's probably a keyboard event. + target_edge_x = display_right ? ShiftDirection::Left : ShiftDirection::Right; + target_edge_y = display_bottom ? ShiftDirection::Up : ShiftDirection::Down; - auto* outer_node = inner_node; - - // break into parent groups when encountering a corner we're dragging in, a - // tab group, or a layout matching the inner_parent. - while (outer_node->parent != nullptr) { - auto& group = outer_node->parent->data.as_group; - - // break out of all layouts that match the orientation of the inner_parent - if (group.layout == inner_parent->data.as_group.layout) goto cont2; - - switch (group.layout) { - case Hy3GroupLayout::Tabbed: - // treat tabbed layouts as if they dont exist during resizing - goto cont2; - case Hy3GroupLayout::SplitH: - if ((drag_x && group.children.back() == outer_node) - || (!drag_x && group.children.front() == outer_node)) - { - goto cont2; - } - break; - case Hy3GroupLayout::SplitV: - if ((drag_y && group.children.back() == outer_node) - || (!drag_y && group.children.front() == outer_node)) - { - goto cont2; - } - break; - } - - break; - cont2: - outer_node = outer_node->parent; - } - - auto& inner_group = inner_parent->data.as_group; - // adjust the inner node - switch (inner_group.layout) { - case Hy3GroupLayout::SplitH: { - auto ratio_mod = - allowed_movement.x * (float) inner_group.children.size() / inner_parent->size.x; - - auto iter = std::find(inner_group.children.begin(), inner_group.children.end(), inner_node); - - if (drag_x) { - if (inner_node == inner_group.children.back()) break; - iter = std::next(iter); - } else { - if (inner_node == inner_group.children.front()) break; - iter = std::prev(iter); - ratio_mod = -ratio_mod; - } - - auto* neighbor = *iter; - - inner_node->size_ratio += ratio_mod; - neighbor->size_ratio -= ratio_mod; - } break; - case Hy3GroupLayout::SplitV: { - auto ratio_mod = allowed_movement.y * (float) inner_parent->data.as_group.children.size() - / inner_parent->size.y; - - auto iter = std::find(inner_group.children.begin(), inner_group.children.end(), inner_node); - - if (drag_y) { - if (inner_node == inner_group.children.back()) break; - iter = std::next(iter); - } else { - if (inner_node == inner_group.children.front()) break; - iter = std::prev(iter); - ratio_mod = -ratio_mod; - } - - auto* neighbor = *iter; - - inner_node->size_ratio += ratio_mod; - neighbor->size_ratio -= ratio_mod; - } break; - case Hy3GroupLayout::Tabbed: break; - } - - inner_parent->recalcSizePosRecursive(*animate == 0); - - if (outer_node != nullptr && outer_node->parent != nullptr) { - auto* outer_parent = outer_node->parent; - auto& outer_group = outer_parent->data.as_group; - // adjust the outer node - switch (outer_group.layout) { - case Hy3GroupLayout::SplitH: { - auto ratio_mod = - allowed_movement.x * (float) outer_group.children.size() / outer_parent->size.x; - - auto iter = std::find(outer_group.children.begin(), outer_group.children.end(), outer_node); - - if (drag_x) { - if (outer_node == inner_group.children.back()) break; - iter = std::next(iter); - } else { - if (outer_node == inner_group.children.front()) break; - iter = std::prev(iter); - ratio_mod = -ratio_mod; + // If the anchor is not at the top/left then reverse the delta + if (target_edge_x == ShiftDirection::Left) resize_delta.x = -resize_delta.x; + if (target_edge_y == ShiftDirection::Up) resize_delta.y = -resize_delta.y; + } else { // It's probably a mouse event + // Resize against the edges corresponding to the selected corner + target_edge_x = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT + ? ShiftDirection::Left + : ShiftDirection::Right; + target_edge_y = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT + ? ShiftDirection::Up + : ShiftDirection::Down; } - auto* neighbor = *iter; + // Find the neighboring node in each axis, which will be either above or at the + // same level as the initiating node in the layout hierarchy. These are the nodes + // which must get resized (rather than the initiator) because they are the + // highest point in the hierarchy + auto horizontal_neighbor = node->findNeighbor(target_edge_x); + auto vertical_neighbor = node->findNeighbor(target_edge_y); - outer_node->size_ratio += ratio_mod; - neighbor->size_ratio -= ratio_mod; - } break; - case Hy3GroupLayout::SplitV: { - auto ratio_mod = allowed_movement.y * (float) outer_parent->data.as_group.children.size() - / outer_parent->size.y; + static const auto animate = ConfigValue("misc:animate_manual_resizes"); - auto iter = std::find(outer_group.children.begin(), outer_group.children.end(), outer_node); - - if (drag_y) { - if (outer_node == outer_group.children.back()) break; - iter = std::next(iter); - } else { - if (outer_node == outer_group.children.front()) break; - iter = std::prev(iter); - ratio_mod = -ratio_mod; + // Note that the resize direction is reversed, because from the neighbor's perspective + // the edge to be moved is the opposite way round. However, the delta is still the same. + if (horizontal_neighbor) { + horizontal_neighbor->resize(reverse(target_edge_x), resize_delta.x, *animate == 0); } - auto* neighbor = *iter; - - outer_node->size_ratio += ratio_mod; - neighbor->size_ratio -= ratio_mod; - } break; - case Hy3GroupLayout::Tabbed: break; + if (vertical_neighbor) { + vertical_neighbor->resize(reverse(target_edge_y), resize_delta.y, *animate == 0); + } } - - outer_parent->recalcSizePosRecursive(*animate == 0); + } else if (window->m_bIsFloating) { + // No parent node - is this a floating window? If so, use the same logic as the `main` layout + const auto required_size = Vector2D( + std::max((window->m_vRealSize.goalv() + delta).x, 20.0), + std::max((window->m_vRealSize.goalv() + delta).y, 20.0) + ); + window->m_vRealSize = required_size; } } @@ -613,13 +543,21 @@ void Hy3Layout::fullscreenRequestForWindow( // Copy of vaxry's massive hack // clang-format off - static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; - static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue; + static const auto gaps_in = ConfigValue("general:gaps_in"); + static const auto gaps_out = ConfigValue("general:gaps_out"); // clang-format on - int outer_gaps = -(*gaps_in - *gaps_out); - auto gap_pos_offset = Vector2D(outer_gaps, outer_gaps); - auto gap_size_offset = Vector2D(outer_gaps * 2, outer_gaps * 2); + // clang-format off + auto gap_pos_offset = Vector2D( + -(gaps_in->left - gaps_out->left), + -(gaps_in->top - gaps_out->top) + ); + // clang-format on + + auto gap_size_offset = Vector2D( + -(gaps_in->left - gaps_out->left) + -(gaps_in->right - gaps_out->right), + -(gaps_in->top - gaps_out->top) + -(gaps_in->bottom - gaps_out->bottom) + ); Hy3Node fakeNode = { .data = window, @@ -701,7 +639,7 @@ CWindow* Hy3Layout::getNextWindowCandidate(CWindow* window) { for (auto& w: g_pCompositor->m_vWindows | std::views::reverse) { if (w->m_bIsMapped && !w->isHidden() && w->m_bIsFloating && w->m_iX11Type != 2 && w->m_iWorkspaceID == window->m_iWorkspaceID && !w->m_bX11ShouldntFocus - && !w->m_bNoFocus && w.get() != window) + && !w->m_sAdditionalConfigData.noFocus && w.get() != window) { return w.get(); } @@ -714,6 +652,7 @@ CWindow* Hy3Layout::getNextWindowCandidate(CWindow* window) { switch (node->data.type) { case Hy3NodeType::Window: return node->data.as_window; case Hy3NodeType::Group: return nullptr; + default: return nullptr; } } @@ -969,6 +908,95 @@ void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible } } +void changeNodeWorkspaceRecursive(Hy3Node& node, CWorkspace* workspace) { + node.workspace_id = workspace->m_iID; + + if (node.data.type == Hy3NodeType::Window) { + auto* window = node.data.as_window; + window->moveToWorkspace(workspace->m_iID); + window->updateToplevel(); + window->updateDynamicRules(); + } else { + for (auto* child: node.data.as_group.children) { + changeNodeWorkspaceRecursive(*child, workspace); + } + } +} + +void Hy3Layout::moveNodeToWorkspace(int origin, std::string wsname, bool follow) { + std::string target_name; + auto target = getWorkspaceIDFromString(wsname, target_name); + + if (target == WORKSPACE_INVALID) { + hy3_log(ERR, "moveNodeToWorkspace called with invalid workspace {}", wsname); + return; + } + + if (origin == target) return; + + auto* node = this->getWorkspaceFocusedNode(origin); + auto* focused_window = g_pCompositor->m_pLastWindow; + auto* focused_window_node = this->getNodeFromWindow(focused_window); + + auto* workspace = g_pCompositor->getWorkspaceByID(target); + + auto wsid = node != nullptr ? node->workspace_id + : focused_window != nullptr ? focused_window->m_iWorkspaceID + : WORKSPACE_INVALID; + + if (wsid == WORKSPACE_INVALID) return; + + auto* origin_ws = g_pCompositor->getWorkspaceByID(wsid); + + if (workspace == nullptr) { + hy3_log(LOG, "creating target workspace {} for node move", target); + + workspace = g_pCompositor->createNewWorkspace(target, origin_ws->m_iMonitorID, target_name); + } + + // floating or fullscreen + if (focused_window != nullptr + && (focused_window_node == nullptr || focused_window->m_bIsFullscreen)) + { + hy3_log(LOG, "{:x}, {:x}", (uintptr_t) focused_window, (uintptr_t) workspace); + g_pCompositor->moveWindowToWorkspaceSafe(focused_window, workspace); + } else { + if (node == nullptr) return; + + hy3_log( + LOG, + "moving node {:x} from workspace {} to workspace {} (follow: {})", + (uintptr_t) node, + origin, + target, + follow + ); + + Hy3Node* expand_actor = nullptr; + node->removeFromParentRecursive(&expand_actor); + if (expand_actor != nullptr) expand_actor->recalcSizePosRecursive(); + + changeNodeWorkspaceRecursive(*node, workspace); + this->insertNode(*node); + } + + if (follow) { + auto* monitor = g_pCompositor->getMonitorFromID(workspace->m_iMonitorID); + + if (workspace->m_bIsSpecialWorkspace) { + monitor->setSpecialWorkspace(workspace); + } else if (origin_ws->m_bIsSpecialWorkspace) { + g_pCompositor->getMonitorFromID(origin_ws->m_iMonitorID)->setSpecialWorkspace(nullptr); + } + + monitor->changeWorkspace(workspace); + + static const auto allow_workspace_cycles = + ConfigValue("binds:allow_workspace_cycles"); + if (*allow_workspace_cycles) workspace->rememberPrevWorkspace(origin_ws); + } +} + void Hy3Layout::changeFocus(int workspace, FocusShift shift) { auto* node = this->getWorkspaceFocusedNode(workspace); if (node == nullptr) return; @@ -1029,18 +1057,18 @@ bottom: Hy3Node* findTabBarAt(Hy3Node& node, Vector2D pos, Hy3Node** focused_node) { // clang-format off - static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; - static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue; - static const auto* tab_bar_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:height")->intValue; - static const auto* tab_bar_padding = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; + static const auto gaps_in = ConfigValue("general:gaps_in"); + static const auto gaps_out = ConfigValue("general:gaps_out"); + static const auto tab_bar_height = ConfigValue("plugin:hy3:tabs:height"); + static const auto tab_bar_padding = ConfigValue("plugin:hy3:tabs:padding"); // clang-format on auto inset = *tab_bar_height + *tab_bar_padding; if (node.parent == nullptr) { - inset += *gaps_out; + inset += gaps_out->left; } else { - inset += *gaps_in; + inset += gaps_in->left; } if (node.data.type == Hy3NodeType::Group) { @@ -1100,8 +1128,13 @@ void Hy3Layout::focusTab( Hy3Node* tab_focused_node; if (target == TabFocus::MouseLocation || mouse != TabFocusMousePriority::Ignore) { - if (g_pCompositor->windowFloatingFromCursor() == nullptr) { - auto mouse_pos = g_pInputManager->getMouseCoordsInternal(); + auto mouse_pos = g_pInputManager->getMouseCoordsInternal(); + if (g_pCompositor->vectorToWindowUnified( + mouse_pos, + RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING | FLOATING_ONLY + ) + == nullptr) + { tab_node = findTabBarAt(*node, mouse_pos, &tab_focused_node); if (tab_node != nullptr) goto hastab; } @@ -1275,12 +1308,12 @@ fullscreen: 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; +// 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()); @@ -1300,17 +1333,19 @@ bool Hy3Layout::shouldRenderSelected(CWindow* window) { switch (focused->data.type) { case Hy3NodeType::Window: return focused->data.as_window == window; - case Hy3NodeType::Group: + case Hy3NodeType::Group: { auto* node = this->getNodeFromWindow(window); if (node == nullptr) return false; return focused->data.as_group.hasChild(node); } + default: return false; + } } Hy3Node* Hy3Layout::getWorkspaceRootGroup(const int& workspace) { for (auto& node: this->nodes) { if (node.workspace_id == workspace && node.parent == nullptr - && node.data.type == Hy3NodeType::Group) + && node.data.type == Hy3NodeType::Group && !node.reparenting) { return &node; } @@ -1441,8 +1476,8 @@ void Hy3Layout::applyNodeDataToWindow(Hy3Node* node, bool no_animation) { } // clang-format off - static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; - static const auto* single_window_no_gaps = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:no_gaps_when_only")->intValue; + static const auto gaps_in = ConfigValue("general:gaps_in"); + static const auto single_window_no_gaps = ConfigValue("plugin:hy3:no_gaps_when_only"); // clang-format on if (!g_pCompositor->windowExists(window) || !window->m_bIsMapped) { @@ -1490,9 +1525,10 @@ void Hy3Layout::applyNodeDataToWindow(Hy3Node* node, bool no_animation) { auto calcPos = window->m_vPosition; auto calcSize = window->m_vSize; - auto gaps_offset_topleft = Vector2D(*gaps_in, *gaps_in) + node->gap_topleft_offset; - auto gaps_offset_bottomright = Vector2D(*gaps_in * 2, *gaps_in * 2) - + node->gap_bottomright_offset + node->gap_topleft_offset; + auto gaps_offset_topleft = Vector2D(gaps_in->left, gaps_in->top) + node->gap_topleft_offset; + auto gaps_offset_bottomright = + Vector2D(gaps_in->left + gaps_in->right, gaps_in->top + gaps_in->bottom) + + node->gap_bottomright_offset + node->gap_topleft_offset; calcPos = calcPos + gaps_offset_topleft; calcSize = calcSize - gaps_offset_bottomright; @@ -1770,8 +1806,8 @@ Hy3Node* Hy3Layout::shiftOrGetFocus( } void Hy3Layout::updateAutotileWorkspaces() { - static const auto* autotile_raw_workspaces = - &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:autotile:workspaces")->strValue; + static const auto autotile_raw_workspaces = + ConfigValue("plugin:hy3:autotile:workspaces"); if (*autotile_raw_workspaces == this->autotile.raw_workspaces) { return; diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp index a165dce..766d0bb 100644 --- a/src/Hy3Layout.hpp +++ b/src/Hy3Layout.hpp @@ -13,9 +13,6 @@ enum class GroupEphemeralityOption { #include -#include "Hy3Node.hpp" -#include "TabGroup.hpp" - enum class ShiftDirection { Left, Up, @@ -23,6 +20,11 @@ enum class ShiftDirection { Right, }; +enum class Axis { None, Horizontal, Vertical }; + +#include "Hy3Node.hpp" +#include "TabGroup.hpp" + enum class FocusShift { Top, Bottom, @@ -91,6 +93,7 @@ public: virtual void onEnable(); virtual void onDisable(); + void insertNode(Hy3Node& node); void makeGroupOnWorkspace(int workspace, Hy3GroupLayout, GroupEphemeralityOption); void makeOppositeGroupOnWorkspace(int workspace, GroupEphemeralityOption); void changeGroupOnWorkspace(int workspace, Hy3GroupLayout); @@ -108,6 +111,7 @@ public: void shiftNode(Hy3Node&, ShiftDirection, bool once, bool visible); void shiftWindow(int workspace, ShiftDirection, bool once, bool visible); void shiftFocus(int workspace, ShiftDirection, bool visible); + void moveNodeToWorkspace(int origin, std::string wsname, bool follow); void changeFocus(int workspace, FocusShift); void focusTab(int workspace, TabFocus target, TabFocusMousePriority, bool wrap_scroll, int index); void setNodeSwallow(int workspace, SetSwallowOption); @@ -142,6 +146,7 @@ private: void updateAutotileWorkspaces(); bool shouldAutotileWorkspace(int); + void resizeNode(Hy3Node*, Vector2D, ShiftDirection resize_edge_x, ShiftDirection resize_edge_y); struct { std::string raw_workspaces; diff --git a/src/Hy3Node.cpp b/src/Hy3Node.cpp index 99c2b47..5eed0fc 100644 --- a/src/Hy3Node.cpp +++ b/src/Hy3Node.cpp @@ -7,6 +7,8 @@ #include "Hy3Node.hpp" #include "globals.hpp" +const float MIN_RATIO = 0.0f; + // Hy3GroupData // Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) { @@ -174,6 +176,7 @@ CWindow* Hy3Node::bringToTop() { } return nullptr; + default: return nullptr; } } @@ -237,6 +240,7 @@ Hy3Node* Hy3Node::getFocusedNode(bool ignore_group_focus, bool stop_at_expanded) stop_at_expanded ); } + default: return nullptr; } } @@ -266,11 +270,23 @@ Hy3Node& Hy3Node::getExpandActor() { void Hy3Node::recalcSizePosRecursive(bool no_animation) { // clang-format off - static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; - static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue; - static const auto* group_inset = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:group_inset")->intValue; - static const auto* tab_bar_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:height")->intValue; - static const auto* tab_bar_padding = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; + static const auto gaps_in = ConfigValue("general:gaps_in"); + static const auto gaps_out = ConfigValue("general:gaps_out"); + static const auto group_inset = ConfigValue("plugin:hy3:group_inset"); + static const auto tab_bar_height = ConfigValue("plugin:hy3:tabs:height"); + static const auto tab_bar_padding = ConfigValue("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) { @@ -283,11 +299,6 @@ void Hy3Node::recalcSizePosRecursive(bool no_animation) { return; } - int outer_gaps = -(*gaps_in - *gaps_out); - - auto gap_topleft_offset = Vector2D(outer_gaps, outer_gaps); - auto gap_bottomright_offset = Vector2D(outer_gaps, outer_gaps); - Hy3Node fake_node = { .data = this->data.as_window, .position = monitor->vecPosition + monitor->vecReservedTopLeft, @@ -301,15 +312,7 @@ void Hy3Node::recalcSizePosRecursive(bool no_animation) { return; } - int outer_gaps = 0; - Vector2D gap_topleft_offset; - Vector2D gap_bottomright_offset; - if (this->parent == nullptr) { - outer_gaps = -(*gaps_in - *gaps_out); - - gap_topleft_offset = Vector2D(outer_gaps, outer_gaps); - gap_bottomright_offset = Vector2D(outer_gaps, outer_gaps); - } else { + if (this->parent != nullptr) { gap_topleft_offset = this->gap_topleft_offset; gap_bottomright_offset = this->gap_bottomright_offset; } @@ -584,6 +587,7 @@ bool Hy3Node::isUrgent() { } return false; + default: return false; } } @@ -759,6 +763,7 @@ Hy3Node* Hy3Node::removeFromParentRecursive(Hy3Node** expand_actor) { } } + this->parent = nullptr; return parent; } @@ -804,6 +809,129 @@ bool Hy3Node::swallowGroups(Hy3Node* into) { 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::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); diff --git a/src/Hy3Node.hpp b/src/Hy3Node.hpp index 4b17d82..f892efb 100644 --- a/src/Hy3Node.hpp +++ b/src/Hy3Node.hpp @@ -1,6 +1,7 @@ #pragma once struct Hy3Node; +struct Hy3GroupData; enum class Hy3GroupLayout; #include @@ -79,6 +80,7 @@ public: struct Hy3Node { Hy3Node* parent = nullptr; + bool reparenting = false; Hy3NodeData data; Vector2D position; Vector2D size; @@ -97,6 +99,9 @@ struct Hy3Node { void markFocused(); void raiseToTop(); Hy3Node* getFocusedNode(bool ignore_group_focus = false, bool stop_at_expanded = false); + Hy3Node* findNeighbor(ShiftDirection); + Hy3Node* getImmediateSibling(ShiftDirection); + void resize(ShiftDirection, double, bool no_animation = false); bool isIndirectlyFocused(); Hy3Node& getExpandActor(); diff --git a/src/TabGroup.cpp b/src/TabGroup.cpp index 27a40d4..0de1c06 100644 --- a/src/TabGroup.cpp +++ b/src/TabGroup.cpp @@ -126,18 +126,18 @@ bool Hy3TabBarEntry::shouldRemove() { void Hy3TabBarEntry::prepareTexture(float scale, CBox& box) { // clang-format off - static const auto* s_rounding = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:rounding")->intValue; - static const auto* render_text = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:render_text")->intValue; - static const auto* text_center = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:text_center")->intValue; - static const auto* text_font = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:text_font")->strValue; - static const auto* text_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:text_height")->intValue; - static const auto* text_padding = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:text_padding")->intValue; - static const auto* col_active = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:col.active")->intValue; - static const auto* col_urgent = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:col.urgent")->intValue; - static const auto* col_inactive = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:col.inactive")->intValue; - static const auto* col_text_active = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:col.text.active")->intValue; - static const auto* col_text_urgent = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:col.text.urgent")->intValue; - static const auto* col_text_inactive = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:col.text.inactive")->intValue; + static const auto s_rounding = ConfigValue("plugin:hy3:tabs:rounding"); + static const auto render_text = ConfigValue("plugin:hy3:tabs:render_text"); + static const auto text_center = ConfigValue("plugin:hy3:tabs:text_center"); + static const auto text_font = ConfigValue("plugin:hy3:tabs:text_font"); + static const auto text_height = ConfigValue("plugin:hy3:tabs:text_height"); + static const auto text_padding = ConfigValue("plugin:hy3:tabs:text_padding"); + static const auto col_active = ConfigValue("plugin:hy3:tabs:col.active"); + static const auto col_urgent = ConfigValue("plugin:hy3:tabs:col.urgent"); + static const auto col_inactive = ConfigValue("plugin:hy3:tabs:col.inactive"); + static const auto col_text_active = ConfigValue("plugin:hy3:tabs:col.text.active"); + static const auto col_text_urgent = ConfigValue("plugin:hy3:tabs:col.text.urgent"); + static const auto col_text_inactive = ConfigValue("plugin:hy3:tabs:col.text.inactive"); // clang-format on auto width = box.width; @@ -225,10 +225,9 @@ void Hy3TabBarEntry::prepareTexture(float scale, CBox& box) { PangoLayout* layout = pango_cairo_create_layout(cairo); pango_layout_set_text(layout, this->window_title.c_str(), -1); - if (*text_center) - pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + if (*text_center) pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); - PangoFontDescription* font_desc = pango_font_description_from_string(text_font->c_str()); + PangoFontDescription* font_desc = pango_font_description_from_string(*text_font); pango_font_description_set_size(font_desc, *text_height * scale * PANGO_SCALE); pango_layout_set_font_description(layout, font_desc); pango_font_description_free(font_desc); @@ -453,17 +452,19 @@ Hy3TabGroup::Hy3TabGroup(Hy3Node& node) { } void Hy3TabGroup::updateWithGroup(Hy3Node& node, bool warp) { - static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; - static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue; - static const auto* bar_height = - &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:height")->intValue; + static const auto gaps_in = ConfigValue("general:gaps_in"); + static const auto gaps_out = ConfigValue("general:gaps_out"); + static const auto bar_height = ConfigValue("plugin:hy3:tabs:height"); - auto gaps = node.parent == nullptr ? *gaps_out : *gaps_in; - auto tpos = node.position + Vector2D(gaps, gaps) + node.gap_topleft_offset; + auto& gaps = node.parent == nullptr ? gaps_out : gaps_in; + auto tpos = node.position + Vector2D(gaps->left, gaps->top) + node.gap_topleft_offset; + + // clang-format off auto tsize = Vector2D( - node.size.x - node.gap_bottomright_offset.x - node.gap_topleft_offset.x - gaps * 2, - *bar_height + node.size.x - node.gap_bottomright_offset.x - node.gap_topleft_offset.x - (gaps->left + gaps->right), + *bar_height ); + // clang-format on this->hidden = node.hidden; if (this->pos.goalv() != tpos) { @@ -485,10 +486,8 @@ void Hy3TabGroup::updateWithGroup(Hy3Node& node, bool warp) { } void Hy3TabGroup::tick() { - static const auto* enter_from_top = - &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:from_top")->intValue; - static const auto* padding = - &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; + static const auto enter_from_top = ConfigValue("plugin:hy3:tabs:from_top"); + static const auto padding = ConfigValue("plugin:hy3:tabs:padding"); auto* workspace = g_pCompositor->getWorkspaceByID(this->workspace_id); this->bar.tick(); @@ -529,12 +528,9 @@ void Hy3TabGroup::tick() { } void Hy3TabGroup::renderTabBar() { - static const auto* window_rounding = - &HyprlandAPI::getConfigValue(PHANDLE, "decoration:rounding")->intValue; - static const auto* enter_from_top = - &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:from_top")->intValue; - static const auto* padding = - &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; + static const auto window_rounding = ConfigValue("decoration:rounding"); + static const auto enter_from_top = ConfigValue("plugin:hy3:tabs:from_top"); + static const auto padding = ConfigValue("plugin:hy3:tabs:padding"); auto* monitor = g_pHyprOpenGL->m_RenderData.pMonitor; auto* workspace = g_pCompositor->getWorkspaceByID(this->workspace_id); diff --git a/src/dispatchers.cpp b/src/dispatchers.cpp index f4d663c..667b1cf 100644 --- a/src/dispatchers.cpp +++ b/src/dispatchers.cpp @@ -6,7 +6,7 @@ #include "dispatchers.hpp" #include "globals.hpp" -int workspace_for_action() { +int workspace_for_action(bool allow_fullscreen = false) { if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return -1; int workspace_id = g_pCompositor->m_pLastMonitor->activeWorkspace; @@ -14,7 +14,7 @@ int workspace_for_action() { if (workspace_id == -1) return -1; auto* workspace = g_pCompositor->getWorkspaceByID(workspace_id); if (workspace == nullptr) return -1; - if (workspace->m_bHasFullscreenWindow) return -1; + if (!allow_fullscreen && workspace->m_bHasFullscreenWindow) return -1; return workspace_id; } @@ -119,6 +119,20 @@ void dispatch_movefocus(std::string value) { } } +void dispatch_move_to_workspace(std::string value) { + int origin_workspace = workspace_for_action(true); + if (origin_workspace == -1) return; + + auto args = CVarList(value); + + auto workspace = args[0]; + if (workspace == "") return; + + bool follow = args[1] == "follow"; + + g_Hy3Layout->moveNodeToWorkspace(origin_workspace, workspace, follow); +} + void dispatch_changefocus(std::string arg) { int workspace = workspace_for_action(); if (workspace == -1) return; @@ -188,7 +202,7 @@ void dispatch_setswallow(std::string arg) { } void dispatch_killactive(std::string value) { - int workspace = workspace_for_action(); + int workspace = workspace_for_action(true); if (workspace == -1) return; g_Hy3Layout->killFocusedNode(workspace); @@ -237,6 +251,7 @@ void registerDispatchers() { HyprlandAPI::addDispatcher(PHANDLE, "hy3:setephemeral", dispatch_setephemeral); HyprlandAPI::addDispatcher(PHANDLE, "hy3:movefocus", dispatch_movefocus); HyprlandAPI::addDispatcher(PHANDLE, "hy3:movewindow", dispatch_movewindow); + HyprlandAPI::addDispatcher(PHANDLE, "hy3:movetoworkspace", dispatch_move_to_workspace); HyprlandAPI::addDispatcher(PHANDLE, "hy3:changefocus", dispatch_changefocus); HyprlandAPI::addDispatcher(PHANDLE, "hy3:focustab", dispatch_focustab); HyprlandAPI::addDispatcher(PHANDLE, "hy3:setswallow", dispatch_setswallow); diff --git a/src/globals.hpp b/src/globals.hpp index f2032d9..48839ca 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -1,6 +1,9 @@ #pragma once +#include + #include +#include #include "Hy3Layout.hpp" #include "log.hpp" @@ -19,3 +22,44 @@ inline void errorNotif() { } ); } + +class HyprlangUnspecifiedCustomType {}; + +// abandon hope all ye who enter here +template +class ConfigValue { +public: + ConfigValue(const std::string& option) { + this->static_data_ptr = HyprlandAPI::getConfigValue(PHANDLE, option)->getDataStaticPtr(); + } + + template + typename std::enable_if::value, const V&>::type + operator*() const { + return *(V*) ((Hyprlang::CUSTOMTYPE*) *this->static_data_ptr)->getData(); + } + + template + typename std::enable_if::value, const V*>::type + operator->() const { + return &**this; + } + + // Bullshit microptimization case for strings + template + typename std::enable_if::value, const char*>::type + operator*() const { + return *(const char**) this->static_data_ptr; + } + + template + typename std::enable_if< + !std::is_same::value && !std::is_same::value, + const T&>::type + operator*() const { + return *(T*) *this->static_data_ptr; + } + +private: + void* const* static_data_ptr; +}; diff --git a/src/main.cpp b/src/main.cpp index 62df130..592ec2c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,3 @@ -#include -#include - #include #include #include @@ -30,38 +27,42 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { selection_hook::init(); #define CONF(NAME, TYPE, VALUE) \ - HyprlandAPI::addConfigValue(PHANDLE, "plugin:hy3:" NAME, SConfigValue {.TYPE##Value = VALUE}) + HyprlandAPI::addConfigValue( \ + PHANDLE, \ + "plugin:hy3:" NAME, \ + Hyprlang::CConfigValue((Hyprlang::TYPE) VALUE) \ + ) // general - CONF("no_gaps_when_only", int, 0); - CONF("node_collapse_policy", int, 2); - CONF("group_inset", int, 10); - CONF("special_scale_factor", float, 0.8); - CONF("tab_first_window", int, 0); + CONF("no_gaps_when_only", INT, 0); + CONF("node_collapse_policy", INT, 2); + CONF("group_inset", INT, 10); + CONF("special_scale_factor", INT, 0.8); + CONF("tab_first_window", INT, 0); // tabs - CONF("tabs:height", int, 15); - CONF("tabs:padding", int, 5); - CONF("tabs:from_top", int, 0); - CONF("tabs:rounding", int, 3); - CONF("tabs:render_text", int, 1); - CONF("tabs:text_center", int, 0); - CONF("tabs:text_font", str, "Sans"); - CONF("tabs:text_height", int, 8); - CONF("tabs:text_padding", int, 3); - CONF("tabs:col.active", int, 0xff32b4ff); - CONF("tabs:col.urgent", int, 0xffff4f4f); - CONF("tabs:col.inactive", int, 0x80808080); - CONF("tabs:col.text.active", int, 0xff000000); - CONF("tabs:col.text.urgent", int, 0xff000000); - CONF("tabs:col.text.inactive", int, 0xff000000); + CONF("tabs:height", INT, 15); + CONF("tabs:padding", INT, 5); + CONF("tabs:from_top", INT, 0); + CONF("tabs:rounding", INT, 3); + CONF("tabs:render_text", INT, 1); + CONF("tabs:text_center", INT, 0); + CONF("tabs:text_font", STRING, "Sans"); + CONF("tabs:text_height", INT, 8); + CONF("tabs:text_padding", INT, 3); + CONF("tabs:col.active", INT, 0xff32b4ff); + CONF("tabs:col.urgent", INT, 0xffff4f4f); + CONF("tabs:col.inactive", INT, 0x80808080); + CONF("tabs:col.text.active", INT, 0xff000000); + CONF("tabs:col.text.urgent", INT, 0xff000000); + CONF("tabs:col.text.inactive", INT, 0xff000000); // autotiling - CONF("autotile:enable", int, 0); - CONF("autotile:ephemeral_groups", int, 1); - CONF("autotile:trigger_height", int, 0); - CONF("autotile:trigger_width", int, 0); - CONF("autotile:workspaces", str, "all"); + CONF("autotile:enable", INT, 0); + CONF("autotile:ephemeral_groups", INT, 1); + CONF("autotile:trigger_height", INT, 0); + CONF("autotile:trigger_width", INT, 0); + CONF("autotile:workspaces", STRING, "all"); #undef CONF