mirror of
https://github.com/Trensa-Organization/hy3.git
synced 2025-03-15 18:53:40 +01:00
Integrate floating windows into hy3:movefocus
Move floating window if focused, even if tiled windows on same workspace Navigate based on window middle Feels unintuitive in use when floating overlaid on tiled Fix: Set new monitor active when moving floating windows, remember previous workspace
This commit is contained in:
parent
cc65dabe32
commit
0e9077ec3d
11 changed files with 567 additions and 128 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,3 +2,5 @@ build/
|
|||
compile_commands.json
|
||||
.vscode/
|
||||
*.log
|
||||
.env
|
||||
.env.*
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
- Implement `resizeactivewindow` for floating windows
|
||||
- Fully implement `resizeactivewindow` for tiled windows
|
||||
|
||||
- Add `hy3:resizenode` dispatcher, drop-in replacement for `resizeactivewindow` applied at the Hy3 group level.
|
||||
- Implement keyboard-based focusing for floating windows
|
||||
- Implement keyboard-based movement for floating windows
|
||||
- Add configuration `kbd_shift_delta` providing delta [in pixels] for shift
|
||||
## hl0.35.0 and before
|
||||
|
||||
- Fixed `hy3:killactive` and `hy3:movetoworkspace` not working in fullscreen.
|
||||
|
|
|
@ -16,6 +16,7 @@ pkg_check_modules(DEPS REQUIRED hyprland pixman-1 libdrm pango pangocairo)
|
|||
add_library(hy3 SHARED
|
||||
src/main.cpp
|
||||
src/dispatchers.cpp
|
||||
src/conversions.cpp
|
||||
src/Hy3Layout.cpp
|
||||
src/Hy3Node.cpp
|
||||
src/TabGroup.cpp
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <regex>
|
||||
#include <set>
|
||||
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <hyprland/src/Compositor.hpp>
|
||||
#include <hyprland/src/plugins/PluginAPI.hpp>
|
||||
#include <ranges>
|
||||
|
@ -8,6 +9,7 @@
|
|||
#include "Hy3Layout.hpp"
|
||||
#include "SelectionHook.hpp"
|
||||
#include "globals.hpp"
|
||||
#include "conversions.hpp"
|
||||
|
||||
std::unique_ptr<HOOK_CALLBACK_FN> renderHookPtr =
|
||||
std::make_unique<HOOK_CALLBACK_FN>(Hy3Layout::renderHook);
|
||||
|
@ -169,7 +171,7 @@ void Hy3Layout::insertNode(Hy3Node& node) {
|
|||
|
||||
if (opening_after != nullptr
|
||||
&& ((node.data.type == Hy3NodeType::Group
|
||||
&& (opening_after == &node || node.data.as_group.hasChild(opening_after)))
|
||||
&& (opening_after == &node || node.hasChild(opening_after)))
|
||||
|| opening_after->reparenting))
|
||||
{
|
||||
opening_after = nullptr;
|
||||
|
@ -287,6 +289,7 @@ void Hy3Layout::insertNode(Hy3Node& node) {
|
|||
}
|
||||
|
||||
void Hy3Layout::onWindowRemovedTiling(CWindow* window) {
|
||||
this->m_focusIntercepts.erase(window);
|
||||
static const auto node_collapse_policy =
|
||||
ConfigValue<Hyprlang::INT>("plugin:hy3:node_collapse_policy");
|
||||
|
||||
|
@ -342,12 +345,16 @@ void Hy3Layout::onWindowRemovedTiling(CWindow* window) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hy3Layout::onWindowRemovedFloating(CWindow *window) {
|
||||
this->m_focusIntercepts.erase(window);
|
||||
}
|
||||
|
||||
void Hy3Layout::onWindowFocusChange(CWindow* window) {
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
if (node == nullptr) return;
|
||||
|
||||
hy3_log(
|
||||
TRACE,
|
||||
LOG,
|
||||
"changing window focus to window {:x} as node {:x}",
|
||||
(uintptr_t) window,
|
||||
(uintptr_t) node
|
||||
|
@ -372,16 +379,10 @@ void Hy3Layout::recalculateMonitor(const int& monitor_id) {
|
|||
// todo: refactor this
|
||||
|
||||
auto* top_node = this->getWorkspaceRootGroup(monitor->activeWorkspace);
|
||||
if (top_node != nullptr) {
|
||||
top_node->position = monitor->vecPosition + monitor->vecReservedTopLeft;
|
||||
top_node->size =
|
||||
monitor->vecSize - monitor->vecReservedTopLeft - monitor->vecReservedBottomRight;
|
||||
|
||||
top_node->recalcSizePosRecursive();
|
||||
if(top_node == nullptr) {
|
||||
top_node = this->getWorkspaceRootGroup(monitor->specialWorkspaceID);
|
||||
}
|
||||
|
||||
top_node = this->getWorkspaceRootGroup(monitor->specialWorkspaceID);
|
||||
|
||||
if (top_node != nullptr) {
|
||||
top_node->position = monitor->vecPosition + monitor->vecReservedTopLeft;
|
||||
top_node->size =
|
||||
|
@ -480,28 +481,31 @@ void executeResizeOperation(const Vector2D& delta, eRectCorner corner, Hy3Node *
|
|||
}
|
||||
|
||||
void Hy3Layout::resizeNode(const Vector2D& delta, eRectCorner corner, Hy3Node* node) {
|
||||
if(node == nullptr) return;
|
||||
|
||||
auto monitor = g_pCompositor->getMonitorFromID(g_pCompositor->getWorkspaceByID(node->workspace_id)->m_iMonitorID);
|
||||
executeResizeOperation(delta, corner, node, monitor);
|
||||
// Is the intended target really a node or a floating window?
|
||||
auto window = g_pCompositor->m_pLastWindow;
|
||||
if(window && window->m_bIsFloating) {
|
||||
this->resizeActiveWindow(delta, corner, window);
|
||||
} else if (node) {
|
||||
auto monitor = g_pCompositor->getMonitorFromID(g_pCompositor->getWorkspaceByID(node->workspace_id)->m_iMonitorID);
|
||||
executeResizeOperation(delta, corner, node, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
void Hy3Layout::resizeActiveWindow(const Vector2D& delta, eRectCorner corner, CWindow* pWindow) {
|
||||
auto window = pWindow ? pWindow : g_pCompositor->m_pLastWindow;
|
||||
if(window == nullptr || ! g_pCompositor->windowValidMapped(window)) return;
|
||||
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
|
||||
if (node != nullptr) {
|
||||
executeResizeOperation(delta, corner, &node->getExpandActor(), g_pCompositor->getMonitorFromID(window->m_iMonitorID));
|
||||
} else if(window->m_bIsFloating) {
|
||||
// No parent node - is this a floating window? If so, use the same logic as the `main` layout
|
||||
if (window->m_bIsFloating) {
|
||||
// Use the same logic as the `main` layout for floating windows
|
||||
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;
|
||||
}
|
||||
g_pXWaylandManager->setWindowSize(window, required_size);
|
||||
} else if (auto* node = this->getNodeFromWindow(window);node != nullptr) {
|
||||
executeResizeOperation(delta, corner, &node->getExpandActor(), g_pCompositor->getMonitorFromID(window->m_iMonitorID));
|
||||
};
|
||||
}
|
||||
|
||||
void Hy3Layout::fullscreenRequestForWindow(
|
||||
|
@ -619,16 +623,26 @@ void Hy3Layout::switchWindows(CWindow* pWindowA, CWindow* pWindowB) {
|
|||
|
||||
void Hy3Layout::moveWindowTo(CWindow* window, const std::string& direction) {
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
if (node == nullptr) return;
|
||||
if (node == nullptr) {
|
||||
const auto neighbor = g_pCompositor->getWindowInDirection(window, direction[0]);
|
||||
|
||||
ShiftDirection shift;
|
||||
if (direction == "l") shift = ShiftDirection::Left;
|
||||
else if (direction == "r") shift = ShiftDirection::Right;
|
||||
else if (direction == "u") shift = ShiftDirection::Up;
|
||||
else if (direction == "d") shift = ShiftDirection::Down;
|
||||
else return;
|
||||
if (window->m_iWorkspaceID != neighbor->m_iWorkspaceID) {
|
||||
// if different monitors, send to monitor
|
||||
onWindowRemovedTiling(window);
|
||||
window->moveToWorkspace(neighbor->m_iWorkspaceID);
|
||||
window->m_iMonitorID = neighbor->m_iMonitorID;
|
||||
onWindowCreatedTiling(window);
|
||||
}
|
||||
} else {
|
||||
ShiftDirection shift;
|
||||
if (direction == "l") shift = ShiftDirection::Left;
|
||||
else if (direction == "r") shift = ShiftDirection::Right;
|
||||
else if (direction == "u") shift = ShiftDirection::Up;
|
||||
else if (direction == "d") shift = ShiftDirection::Down;
|
||||
else return;
|
||||
|
||||
this->shiftNode(*node, shift, false, false);
|
||||
this->shiftNode(*node, shift, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Hy3Layout::alterSplitRatio(CWindow* pWindow, float delta, bool exact) {
|
||||
|
@ -878,46 +892,369 @@ void Hy3Layout::shiftNode(Hy3Node& node, ShiftDirection direction, bool once, bo
|
|||
}
|
||||
}
|
||||
|
||||
void Hy3Layout::shiftWindow(int workspace, ShiftDirection direction, bool once, bool visible) {
|
||||
auto* node = this->getWorkspaceFocusedNode(workspace);
|
||||
if (node == nullptr) return;
|
||||
void shiftFloatingWindow(CWindow* window, ShiftDirection direction) {
|
||||
static const auto* kbd_shift_delta =
|
||||
&HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:kbd_shift_delta")->intValue;
|
||||
|
||||
this->shiftNode(*node, direction, once, visible);
|
||||
}
|
||||
if(!window) return;
|
||||
|
||||
void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible) {
|
||||
auto* current_window = g_pCompositor->m_pLastWindow;
|
||||
|
||||
if (current_window != nullptr) {
|
||||
auto* p_workspace = g_pCompositor->getWorkspaceByID(current_window->m_iWorkspaceID);
|
||||
if (p_workspace->m_bHasFullscreenWindow) return;
|
||||
|
||||
if (current_window->m_bIsFloating) {
|
||||
auto* next_window = g_pCompositor->getWindowInDirection(
|
||||
current_window,
|
||||
direction == ShiftDirection::Left ? 'l'
|
||||
: direction == ShiftDirection::Up ? 'u'
|
||||
: direction == ShiftDirection::Down ? 'd'
|
||||
: 'r'
|
||||
);
|
||||
|
||||
if (next_window != nullptr) g_pCompositor->focusWindow(next_window);
|
||||
return;
|
||||
Vector2D bounds {0, 0};
|
||||
// BUG: Assumes horizontal monitor layout
|
||||
// BUG: Ignores monitor reserved space
|
||||
for(auto m: g_pCompositor->m_vMonitors) {
|
||||
bounds.x = std::max(bounds.x, m->vecPosition.x + m->vecSize.x);
|
||||
if(m->ID == window->m_iMonitorID) {
|
||||
bounds.y = m->vecPosition.y + m->vecSize.y;
|
||||
}
|
||||
}
|
||||
|
||||
auto* node = this->getWorkspaceFocusedNode(workspace);
|
||||
if (node == nullptr) return;
|
||||
const int delta = getSearchDirection(direction) == SearchDirection::Forwards
|
||||
? *kbd_shift_delta: -*kbd_shift_delta;
|
||||
|
||||
auto* target = this->shiftOrGetFocus(*node, direction, false, false, visible);
|
||||
Vector2D movement_delta = (getAxis(direction) == Axis::Horizontal)
|
||||
? Vector2D{ delta, 0 } : Vector2D{ 0, delta };
|
||||
|
||||
if (target != nullptr) {
|
||||
target->focus();
|
||||
while (target->parent != nullptr) target = target->parent;
|
||||
target->recalcSizePosRecursive();
|
||||
auto window_pos = window->m_vRealPosition.vec();
|
||||
auto window_size = window->m_vRealSize.vec();
|
||||
|
||||
// Keep at least `delta` pixels visible
|
||||
if (window_pos.x + window_size.x + delta < 0 || window_pos.x + delta > bounds.x) movement_delta.x = 0;
|
||||
if (window_pos.y + window_size.y + delta < 0 || window_pos.y + delta > bounds.y) movement_delta.y = 0;
|
||||
if(movement_delta.x != 0 || movement_delta.y != 0) {
|
||||
auto new_pos = window_pos + movement_delta;
|
||||
// Do we need to change the workspace?
|
||||
const auto new_monitor = g_pCompositor->getMonitorFromVector(new_pos);
|
||||
if(new_monitor && new_monitor->ID != window->m_iMonitorID) {
|
||||
// Ignore the movement request if the new workspace is special
|
||||
if(! new_monitor->specialWorkspaceID) {
|
||||
const auto old_workspace = g_pCompositor->getWorkspaceByID(window->m_iWorkspaceID);
|
||||
const auto new_workspace = g_pCompositor->getWorkspaceByID(new_monitor->activeWorkspace);
|
||||
const auto previous_monitor = g_pCompositor->getMonitorFromID(window->m_iMonitorID);
|
||||
const auto original_new_pos = new_pos;
|
||||
|
||||
if(new_workspace && previous_monitor) {
|
||||
switch (direction)
|
||||
{
|
||||
case ShiftDirection::Left: new_pos.x += new_monitor->vecSize.x; break;
|
||||
case ShiftDirection::Right: new_pos.x -= previous_monitor->vecSize.x; break;
|
||||
case ShiftDirection::Up: new_pos.y += new_monitor->vecSize.y; break;
|
||||
case ShiftDirection::Down: new_pos.y -= previous_monitor->vecSize.y; break;
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
window->m_vRealPosition = new_pos;
|
||||
g_pCompositor->moveWindowToWorkspaceSafe(window, new_workspace);
|
||||
g_pCompositor->setActiveMonitor(new_monitor);
|
||||
|
||||
static auto* const allow_workspace_cycles =
|
||||
&g_pConfigManager->getConfigValuePtr("binds:allow_workspace_cycles")->intValue;
|
||||
if (*allow_workspace_cycles) new_workspace->rememberPrevWorkspace(old_workspace);
|
||||
}
|
||||
} else {
|
||||
window->m_vRealPosition = new_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hy3Layout::shiftWindow(int workspace_id, ShiftDirection direction, bool once, bool visible) {
|
||||
auto focused_window = g_pCompositor->m_pLastWindow;
|
||||
auto* node = getWorkspaceFocusedNode(workspace_id);
|
||||
|
||||
if (focused_window && focused_window->m_bIsFloating) {
|
||||
shiftFloatingWindow(focused_window, direction);
|
||||
} else if (node) {
|
||||
shiftNode(*node, direction, once, visible);
|
||||
}
|
||||
}
|
||||
|
||||
void Hy3Layout::focusMonitor(CMonitor* monitor) {
|
||||
if(monitor == nullptr) return;
|
||||
|
||||
g_pCompositor->setActiveMonitor(monitor);
|
||||
const auto focusedNode = this->getWorkspaceFocusedNode(monitor->activeWorkspace);
|
||||
if(focusedNode != nullptr) {
|
||||
focusedNode->focus();
|
||||
} else {
|
||||
auto* workspace = g_pCompositor->getWorkspaceByID(monitor->activeWorkspace);
|
||||
CWindow* next_window = nullptr;
|
||||
if(workspace != nullptr) {
|
||||
workspace->setActive(true);
|
||||
if (workspace->m_bHasFullscreenWindow) {
|
||||
next_window = g_pCompositor->getFullscreenWindowOnWorkspace(workspace->m_iID);
|
||||
} else {
|
||||
next_window = workspace->getLastFocusedWindow();
|
||||
}
|
||||
} else {
|
||||
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 == next_window->m_iWorkspaceID && !w->m_bX11ShouldntFocus
|
||||
&& !w->m_bNoFocus)
|
||||
{
|
||||
next_window = w.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_pCompositor->focusWindow(next_window);
|
||||
}
|
||||
}
|
||||
|
||||
CWindow *getFocusedWindow(const Hy3Node *node) {
|
||||
auto search = node;
|
||||
while(search != nullptr && search->data.type == Hy3NodeType::Group) {
|
||||
search = search->data.as_group.focused_child;
|
||||
}
|
||||
|
||||
if(search == nullptr || search->data.type != Hy3NodeType::Window) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return search->data.as_window;
|
||||
}
|
||||
|
||||
bool shiftIsForward(ShiftDirection direction) {
|
||||
return direction == ShiftDirection::Right || direction == ShiftDirection::Down;
|
||||
}
|
||||
|
||||
bool shiftIsVertical(ShiftDirection direction) {
|
||||
return direction == ShiftDirection::Up || direction == ShiftDirection::Down;
|
||||
}
|
||||
|
||||
bool shiftMatchesLayout(Hy3GroupLayout layout, ShiftDirection direction) {
|
||||
return (layout == Hy3GroupLayout::SplitV && shiftIsVertical(direction))
|
||||
|| (layout != Hy3GroupLayout::SplitV && !shiftIsVertical(direction));
|
||||
}
|
||||
|
||||
|
||||
bool covers(const CBox &outer, const CBox &inner) {
|
||||
return outer.x <= inner.x
|
||||
&& outer.y <= inner.y
|
||||
&& outer.x + outer.w >= inner.x + inner.w
|
||||
&& outer.y + outer.h >= inner.y + inner.h;
|
||||
}
|
||||
|
||||
bool isObscured (CWindow* window) {
|
||||
if(!window) return false;
|
||||
|
||||
const auto inner_box = window->getWindowMainSurfaceBox();
|
||||
|
||||
bool is_obscured = false;
|
||||
for(auto& w :g_pCompositor->m_vWindows | std::views::reverse) {
|
||||
if(w.get() == window) {
|
||||
hy3_log(LOG, "{} doesn't obscure itself", window);
|
||||
// Don't go any further if this is a floating window, because m_vWindows is sorted bottom->top per Compositor.cpp
|
||||
if(window->m_bIsFloating) break; else continue;
|
||||
}
|
||||
|
||||
if(!w->m_bIsFloating) {
|
||||
hy3_log(LOG, "Tiled window {} can't obscure anything", w.get());
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto outer_box = w->getWindowMainSurfaceBox();
|
||||
is_obscured = covers(outer_box, inner_box);
|
||||
|
||||
if(is_obscured) {
|
||||
hy3_log(LOG, "{} obscures {}", w.get(), window);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return is_obscured;
|
||||
}
|
||||
|
||||
bool isObscured(Hy3Node* node) {
|
||||
return node && node->data.type == Hy3NodeType::Window && isObscured(node->data.as_window);
|
||||
}
|
||||
|
||||
bool isNotObscured(CWindow* window) { return !isObscured(window); }
|
||||
bool isNotObscured(Hy3Node* node) { return !isObscured(node); }
|
||||
|
||||
CWindow* getWindowInDirection(CWindow* source, ShiftDirection direction, bool considerFloating, bool considerTiled, bool considerOtherMonitors) {
|
||||
if(!source) return nullptr;
|
||||
|
||||
CWindow *target_window = nullptr;
|
||||
const auto current_surface_box = source->getWindowMainSurfaceBox();
|
||||
auto target_distance = Distance { direction };
|
||||
|
||||
auto isCandidate = [=, mon = source->m_iMonitorID](CWindow* w) {
|
||||
return (considerOtherMonitors || w->m_iMonitorID == mon)
|
||||
&& ((considerFloating && w->m_bIsFloating) || (considerTiled && !w->m_bIsFloating) || (w->m_iMonitorID != mon))
|
||||
&& w->m_bIsMapped
|
||||
&& w->m_iX11Type != 2
|
||||
&& !w->m_bNoFocus
|
||||
&& !w->isHidden()
|
||||
&& !w->m_bX11ShouldntFocus;
|
||||
};
|
||||
|
||||
for(auto &pw: g_pCompositor->m_vWindows) {
|
||||
auto w = pw.get();
|
||||
if(w != source && isCandidate(w)) {
|
||||
auto dist = Distance { direction, current_surface_box, w->getWindowMainSurfaceBox() };
|
||||
if((dist < target_distance || (target_distance.isNotInitialised() && dist.isInDirection(direction))) && isNotObscured(w) ) {
|
||||
target_window = w;
|
||||
target_distance = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hy3_log(LOG, "getWindowInDirection: closest window to {} is {}", source, target_window);
|
||||
return target_window;
|
||||
}
|
||||
|
||||
void Hy3Layout::shiftFocusToMonitor(ShiftDirection direction) {
|
||||
auto target_monitor = g_pCompositor->getMonitorInDirection(directionToChar(direction));
|
||||
if(target_monitor) this->focusMonitor(target_monitor);
|
||||
}
|
||||
|
||||
void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible) {
|
||||
Hy3Node *candidate_node = nullptr;
|
||||
CWindow *closest_window = nullptr;
|
||||
Hy3Node *source_node = nullptr;
|
||||
CWindow *source_window = g_pCompositor->m_pLastWindow;
|
||||
CWorkspace *source_workspace = g_pCompositor->getWorkspaceByID(workspace);
|
||||
|
||||
if(source_window == nullptr) {
|
||||
shiftFocusToMonitor(direction);
|
||||
return;
|
||||
}
|
||||
|
||||
if(source_workspace == nullptr) return;
|
||||
|
||||
hy3_log(LOG,
|
||||
"shiftFocus: Source: {} ({}), workspace: {}, direction: {}, visible: {}",
|
||||
source_window,
|
||||
source_window->m_bIsFloating ? "floating" : "tiled",
|
||||
workspace,
|
||||
(int)direction,
|
||||
visible
|
||||
);
|
||||
|
||||
// Determine the starting point for looking for a tiled node - it's either the
|
||||
// workspace's focused node or the floating window's focus entry point (which may be null)
|
||||
source_node = source_window->m_bIsFloating ? getFocusOverride(source_window, direction)
|
||||
: getWorkspaceFocusedNode(workspace);
|
||||
|
||||
// Get the closest node to the starting point
|
||||
if(source_node) {
|
||||
candidate_node = this->shiftOrGetFocus(*source_node, direction, false, false, visible);
|
||||
while(candidate_node && !isNotObscured(candidate_node)) {
|
||||
candidate_node = this->shiftOrGetFocus(*candidate_node, direction, false, false, visible);
|
||||
}
|
||||
}
|
||||
|
||||
bool select_tiled_windows = source_window->m_bIsFloating && !candidate_node;
|
||||
|
||||
// Find the closest window in the right direction. Only consider tiled windows or other monitors
|
||||
// if `shiftOrGetFocus` didn't provide a candidate.
|
||||
closest_window = getWindowInDirection(source_window, direction, true, select_tiled_windows, !candidate_node);
|
||||
|
||||
// If there's a window in the right direction then choose between that window and the tiled candidate.
|
||||
bool focus_closest_window = false;
|
||||
if(closest_window) {
|
||||
if(candidate_node) {
|
||||
// If the closest window is tiled then focus the tiled node which was obtained from `shiftOrGetFocus`,
|
||||
// otherwise focus whichever is closer
|
||||
if(closest_window->m_bIsFloating) {
|
||||
const auto source_box = source_window->getWindowMainSurfaceBox();
|
||||
Distance distanceToClosestWindow(direction, source_box, closest_window->getWindowMainSurfaceBox());
|
||||
Distance distanceToTiledNode(direction, source_box, candidate_node->getMainSurfaceBox());
|
||||
if(distanceToClosestWindow < distanceToTiledNode) {
|
||||
focus_closest_window = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
focus_closest_window = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto new_monitor_id = source_window->m_iMonitorID;
|
||||
|
||||
if(focus_closest_window) {
|
||||
new_monitor_id = closest_window->m_iMonitorID;
|
||||
setFocusOverride(closest_window, direction, source_node);
|
||||
g_pCompositor->focusWindow(closest_window);
|
||||
} else if(candidate_node) {
|
||||
if(candidate_node->data.type == Hy3NodeType::Window) {
|
||||
new_monitor_id = candidate_node->data.as_window->m_iMonitorID;
|
||||
}
|
||||
candidate_node->focusWindow();
|
||||
candidate_node->getRoot()->recalcSizePosRecursive();
|
||||
} else {
|
||||
shiftFocusToMonitor(direction);
|
||||
}
|
||||
|
||||
if(new_monitor_id != source_window->m_iMonitorID) {
|
||||
if(auto *monitor = g_pCompositor->getMonitorFromID(new_monitor_id)) {
|
||||
g_pCompositor->setActiveMonitor(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Hy3Node* Hy3Layout::getFocusOverride(CWindow* src, ShiftDirection direction) {
|
||||
if(auto intercept = this->m_focusIntercepts.find(src); intercept != this->m_focusIntercepts.end()) {
|
||||
hy3_log(LOG, "getFocusOverride: Found intercept for {}", src);
|
||||
Hy3Node** accessor;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case ShiftDirection::Left: accessor = &intercept->second.left; break;
|
||||
case ShiftDirection::Up: accessor = &intercept->second.up; break;
|
||||
case ShiftDirection::Right: accessor = &intercept->second.right; break;
|
||||
case ShiftDirection::Down: accessor = &intercept->second.down; break;
|
||||
default: hy3_log(WARN, "Unknown ShiftDirection: {}", (int) direction); return nullptr;
|
||||
}
|
||||
|
||||
if(auto override = *accessor) {
|
||||
// If the root isn't valid or is on a different workspsace then update the intercept data
|
||||
if(override->workspace_id != src->m_iWorkspaceID || !std::ranges::contains(this->nodes, *override)) {
|
||||
override = nullptr;
|
||||
*accessor = nullptr;
|
||||
// If there are no remaining overrides then discard the intercept
|
||||
if(intercept->second.left == nullptr
|
||||
&& intercept->second.up == nullptr
|
||||
&& intercept->second.right == nullptr
|
||||
&& intercept->second.down == nullptr
|
||||
) {
|
||||
this->m_focusIntercepts.erase(intercept);
|
||||
}
|
||||
}
|
||||
|
||||
return override;
|
||||
}
|
||||
}
|
||||
|
||||
hy3_log(LOG, "getFocusOverride: No intercept found for: {}", src);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Hy3Layout::setFocusOverride(CWindow* src, ShiftDirection direction, Hy3Node* dest) {
|
||||
hy3_log(LOG, "setFocusOverride: Storing intercept for: {} to: {:x}", src, (uintptr_t)dest);
|
||||
if(auto intercept = this->m_focusIntercepts.find(src);intercept != this->m_focusIntercepts.end()) {
|
||||
hy3_log(LOG, "setFocusOverride: Updating existing intercept");
|
||||
switch(direction) {
|
||||
case ShiftDirection::Left: intercept->second.left = dest; break;
|
||||
case ShiftDirection::Up: intercept->second.up = dest; break;
|
||||
case ShiftDirection::Right: intercept->second.right = dest; break;
|
||||
case ShiftDirection::Down: intercept->second.down = dest; break;
|
||||
default: hy3_log(WARN, "Unknown ShiftDirection: {}", (int) direction);
|
||||
}
|
||||
} else {
|
||||
hy3_log(LOG, "setFocusOverride: Adding new intercept");
|
||||
FocusOverride override;
|
||||
switch(direction) {
|
||||
case ShiftDirection::Left: override.left = dest; break;
|
||||
case ShiftDirection::Up: override.up = dest; break;
|
||||
case ShiftDirection::Right: override.right = dest; break;
|
||||
case ShiftDirection::Down: override.down = dest; break;
|
||||
default: hy3_log(WARN, "Unknown ShiftDirection: {}", (int) direction);
|
||||
}
|
||||
this->m_focusIntercepts.insert({ src, override });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void changeNodeWorkspaceRecursive(Hy3Node& node, CWorkspace* workspace) {
|
||||
node.workspace_id = workspace->m_iID;
|
||||
|
||||
|
@ -1346,7 +1683,7 @@ bool Hy3Layout::shouldRenderSelected(CWindow* window) {
|
|||
case Hy3NodeType::Group: {
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
if (node == nullptr) return false;
|
||||
return focused->data.as_group.hasChild(node);
|
||||
return focused->hasChild(node);
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
|
@ -1568,19 +1905,6 @@ void Hy3Layout::applyNodeDataToWindow(Hy3Node* node, bool no_animation) {
|
|||
}
|
||||
}
|
||||
|
||||
bool shiftIsForward(ShiftDirection direction) {
|
||||
return direction == ShiftDirection::Right || direction == ShiftDirection::Down;
|
||||
}
|
||||
|
||||
bool shiftIsVertical(ShiftDirection direction) {
|
||||
return direction == ShiftDirection::Up || direction == ShiftDirection::Down;
|
||||
}
|
||||
|
||||
bool shiftMatchesLayout(Hy3GroupLayout layout, ShiftDirection direction) {
|
||||
return (layout == Hy3GroupLayout::SplitV && shiftIsVertical(direction))
|
||||
|| (layout != Hy3GroupLayout::SplitV && !shiftIsVertical(direction));
|
||||
}
|
||||
|
||||
Hy3Node* Hy3Layout::shiftOrGetFocus(
|
||||
Hy3Node& node,
|
||||
ShiftDirection direction,
|
||||
|
@ -1663,10 +1987,14 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(
|
|||
std::list<Hy3Node*>::iterator insert;
|
||||
|
||||
if (break_origin == parent_group.children.front() && !shiftIsForward(direction)) {
|
||||
if (!shift) return nullptr;
|
||||
if (!shift) {
|
||||
return nullptr;
|
||||
}
|
||||
insert = parent_group.children.begin();
|
||||
} else if (break_origin == parent_group.children.back() && shiftIsForward(direction)) {
|
||||
if (!shift) return nullptr;
|
||||
if (!shift) {
|
||||
return nullptr;
|
||||
}
|
||||
insert = parent_group.children.end();
|
||||
} else {
|
||||
auto& group_data = target_group->data.as_group;
|
||||
|
@ -1688,14 +2016,18 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(
|
|||
if (shiftIsForward(direction)) insert = iter;
|
||||
else insert = std::next(iter);
|
||||
}
|
||||
} else return (*iter)->getFocusedNode();
|
||||
} else {
|
||||
return (*iter)->getFocusedNode();
|
||||
}
|
||||
} else {
|
||||
// break into neighboring groups until we hit a window
|
||||
while (true) {
|
||||
target_group = *iter;
|
||||
auto& group_data = target_group->data.as_group;
|
||||
|
||||
if (group_data.children.empty()) return nullptr; // in theory this would never happen
|
||||
if (group_data.children.empty()) {
|
||||
return nullptr;
|
||||
} // in theory this would never happen
|
||||
|
||||
bool shift_after = false;
|
||||
|
||||
|
@ -1781,7 +2113,7 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(
|
|||
if (old_parent != nullptr) {
|
||||
auto& group = old_parent->data.as_group;
|
||||
if (old_parent->parent != nullptr && group.ephemeral && group.children.size() == 1
|
||||
&& !group.hasChild(&node))
|
||||
&& !old_parent->hasChild(&node))
|
||||
{
|
||||
Hy3Node::swallowGroups(old_parent);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
#pragma once
|
||||
#include <list>
|
||||
#include <set>
|
||||
|
||||
#include <hyprland/src/layout/IHyprLayout.hpp>
|
||||
#include <map>
|
||||
|
||||
class Hy3Layout;
|
||||
|
||||
|
@ -8,11 +13,6 @@ enum class GroupEphemeralityOption {
|
|||
ForceEphemeral,
|
||||
};
|
||||
|
||||
#include <list>
|
||||
#include <set>
|
||||
|
||||
#include <hyprland/src/layout/IHyprLayout.hpp>
|
||||
|
||||
enum class ShiftDirection {
|
||||
Left,
|
||||
Up,
|
||||
|
@ -20,10 +20,17 @@ enum class ShiftDirection {
|
|||
Right,
|
||||
};
|
||||
|
||||
enum class SearchDirection {
|
||||
None,
|
||||
Forwards,
|
||||
Backwards
|
||||
};
|
||||
|
||||
enum class Axis { None, Horizontal, Vertical };
|
||||
|
||||
#include "Hy3Node.hpp"
|
||||
#include "TabGroup.hpp"
|
||||
#include "conversions.hpp"
|
||||
|
||||
enum class FocusShift {
|
||||
Top,
|
||||
|
@ -67,17 +74,25 @@ enum class ExpandFullscreenOption {
|
|||
MaximizeAsFullscreen,
|
||||
};
|
||||
|
||||
struct FocusOverride {
|
||||
Hy3Node *left = nullptr;
|
||||
Hy3Node *up = nullptr;
|
||||
Hy3Node *right = nullptr;
|
||||
Hy3Node *down = nullptr;
|
||||
};
|
||||
|
||||
|
||||
class Hy3Layout: public IHyprLayout {
|
||||
public:
|
||||
virtual void onWindowCreated(CWindow*, eDirection = DIRECTION_DEFAULT);
|
||||
virtual void onWindowCreatedTiling(CWindow*, eDirection = DIRECTION_DEFAULT);
|
||||
virtual void onWindowRemovedTiling(CWindow*);
|
||||
virtual void onWindowRemovedFloating(CWindow*);
|
||||
virtual void onWindowFocusChange(CWindow*);
|
||||
virtual bool isWindowTiled(CWindow*);
|
||||
virtual void recalculateMonitor(const int& monitor_id);
|
||||
virtual void recalculateWindow(CWindow*);
|
||||
virtual void
|
||||
resizeActiveWindow(const Vector2D& delta, eRectCorner corner, CWindow* pWindow = nullptr);
|
||||
virtual void resizeActiveWindow(const Vector2D& delta, eRectCorner corner, CWindow* pWindow = nullptr);
|
||||
virtual void fullscreenRequestForWindow(CWindow*, eFullscreenMode, bool enable_fullscreen);
|
||||
virtual std::any layoutMessage(SLayoutMessageHeader header, std::string content);
|
||||
virtual SWindowRenderLayoutHints requestRenderHints(CWindow*);
|
||||
|
@ -139,6 +154,10 @@ public:
|
|||
private:
|
||||
Hy3Node* getNodeFromWindow(CWindow*);
|
||||
void applyNodeDataToWindow(Hy3Node*, bool no_animation = false);
|
||||
void shiftFocusToMonitor(ShiftDirection direction);
|
||||
std::unordered_map<CWindow*, FocusOverride> m_focusIntercepts;
|
||||
Hy3Node* getFocusOverride(CWindow* src, ShiftDirection direction);
|
||||
void setFocusOverride(CWindow* src, ShiftDirection direction, Hy3Node* dest);
|
||||
|
||||
// if shift is true, shift the window in the given direction, returning
|
||||
// nullptr, if shift is false, return the window in the given direction or
|
||||
|
@ -147,6 +166,8 @@ private:
|
|||
|
||||
void updateAutotileWorkspaces();
|
||||
bool shouldAutotileWorkspace(int);
|
||||
void resizeNode(Hy3Node*, Vector2D, ShiftDirection resize_edge_x, ShiftDirection resize_edge_y);
|
||||
void focusMonitor(CMonitor*);
|
||||
|
||||
struct {
|
||||
std::string raw_workspaces;
|
||||
|
|
|
@ -33,18 +33,6 @@ 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;
|
||||
|
@ -195,20 +183,17 @@ void markGroupFocusedRecursive(Hy3GroupData& 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;
|
||||
// undo decos for root focus
|
||||
auto* root = node;
|
||||
while (root->parent != nullptr) {
|
||||
root->parent->data.as_group.focused_child = root;
|
||||
root->parent->data.as_group.group_focused = false;
|
||||
root = root->parent;
|
||||
}
|
||||
|
||||
root->updateDecos();
|
||||
|
@ -842,24 +827,6 @@ Hy3Node* Hy3Node::getImmediateSibling(ShiftDirection direction) {
|
|||
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;
|
||||
|
@ -894,6 +861,10 @@ int directionToIteratorIncrement(ShiftDirection direction) {
|
|||
}
|
||||
}
|
||||
|
||||
CBox Hy3Node::getMainSurfaceBox() {
|
||||
return { this->position, this->size };
|
||||
}
|
||||
|
||||
void Hy3Node::resize(ShiftDirection direction, double delta, bool no_animation) {
|
||||
auto& parent_node = this->parent;
|
||||
auto& containing_group = parent_node->data.as_group;
|
||||
|
@ -951,3 +922,19 @@ void Hy3Node::swapData(Hy3Node& a, Hy3Node& b) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Hy3Node::hasChild(Hy3Node* node) {
|
||||
if(this->data.type == Hy3NodeType::Window) return false;
|
||||
|
||||
auto n = node;
|
||||
while(n != nullptr && n->parent != this) n = n->parent;
|
||||
|
||||
return n != nullptr;
|
||||
}
|
||||
|
||||
Hy3Node* Hy3Node::getRoot() {
|
||||
Hy3Node* maybeRoot = this;
|
||||
while(maybeRoot->parent) maybeRoot = maybeRoot->parent;
|
||||
|
||||
return maybeRoot;
|
||||
}
|
|
@ -10,6 +10,7 @@ enum class Hy3GroupLayout;
|
|||
|
||||
#include "Hy3Layout.hpp"
|
||||
#include "TabGroup.hpp"
|
||||
#include "conversions.hpp"
|
||||
|
||||
enum class Hy3GroupLayout {
|
||||
SplitH,
|
||||
|
@ -42,7 +43,6 @@ struct Hy3GroupData {
|
|||
Hy3GroupData(Hy3GroupLayout layout);
|
||||
~Hy3GroupData();
|
||||
|
||||
bool hasChild(Hy3Node* child);
|
||||
void collapseExpansions();
|
||||
void setLayout(Hy3GroupLayout layout);
|
||||
void setEphemeral(GroupEphemeralityOption ephemeral);
|
||||
|
@ -101,9 +101,11 @@ struct Hy3Node {
|
|||
Hy3Node* getFocusedNode(bool ignore_group_focus = false, bool stop_at_expanded = false);
|
||||
Hy3Node* findNeighbor(ShiftDirection);
|
||||
Hy3Node* getImmediateSibling(ShiftDirection);
|
||||
Hy3Node* getRoot();
|
||||
void resize(ShiftDirection, double, bool no_animation = false);
|
||||
bool isIndirectlyFocused();
|
||||
Hy3Node& getExpandActor();
|
||||
CBox getMainSurfaceBox();
|
||||
|
||||
void recalcSizePosRecursive(bool no_animation = false);
|
||||
void updateTabBar(bool no_animation = false);
|
||||
|
@ -116,6 +118,7 @@ struct Hy3Node {
|
|||
|
||||
Hy3Node* findNodeForTabGroup(Hy3TabGroup&);
|
||||
void appendAllWindows(std::vector<CWindow*>&);
|
||||
bool hasChild(Hy3Node* child);
|
||||
std::string debugNode();
|
||||
|
||||
// Remove this node from its parent, deleting the parent if it was
|
||||
|
@ -131,3 +134,35 @@ struct Hy3Node {
|
|||
static bool swallowGroups(Hy3Node* into);
|
||||
static void swapData(Hy3Node&, Hy3Node&);
|
||||
};
|
||||
|
||||
struct Distance {
|
||||
bool is_forward;
|
||||
double primary_axis = -1;
|
||||
double secondary_axis = -1;
|
||||
bool operator< (Distance other) {
|
||||
return isInitialised()
|
||||
&& other.isInitialised()
|
||||
&& is_forward == other.is_forward
|
||||
&& (primary_axis < other.primary_axis || (primary_axis == other.primary_axis && secondary_axis < other.secondary_axis));
|
||||
}
|
||||
bool isInitialised() { return primary_axis != -1; }
|
||||
bool isNotInitialised() { return primary_axis == -1; }
|
||||
bool isSameDirection(Distance other) {
|
||||
return other.primary_axis != 0 && other.is_forward == is_forward;
|
||||
}
|
||||
bool isInDirection(ShiftDirection direction) {
|
||||
bool direction_is_forward = getSearchDirection(direction) == SearchDirection::Forwards;
|
||||
return is_forward == direction_is_forward;
|
||||
}
|
||||
Distance(ShiftDirection direction) {
|
||||
is_forward = getSearchDirection(direction) == SearchDirection::Forwards;
|
||||
}
|
||||
Distance(ShiftDirection direction, CBox from, CBox to) {
|
||||
auto middle_from = from.middle(), middle_to = to.middle();
|
||||
auto primary_dist = getAxis(direction) == Axis::Horizontal ? middle_from.x - middle_to.x : middle_from.y - middle_to.y;
|
||||
|
||||
is_forward = std::signbit(primary_dist);
|
||||
primary_axis = abs(primary_dist);
|
||||
secondary_axis = abs(getAxis(direction) == Axis::Horizontal ? middle_from.y - middle_to.y : middle_from.x - middle_to.x);
|
||||
}
|
||||
};
|
51
src/conversions.cpp
Normal file
51
src/conversions.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "Hy3Node.hpp"
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
SearchDirection getSearchDirection(ShiftDirection direction) {
|
||||
switch(direction) {
|
||||
case ShiftDirection::Left:
|
||||
case ShiftDirection::Up:
|
||||
return SearchDirection::Backwards;
|
||||
case ShiftDirection::Right:
|
||||
case ShiftDirection::Down:
|
||||
return SearchDirection::Forwards;
|
||||
default:
|
||||
return SearchDirection::None;
|
||||
}
|
||||
}
|
||||
|
||||
char directionToChar(ShiftDirection direction) {
|
||||
switch (direction)
|
||||
{
|
||||
case ShiftDirection::Left: return 'l';
|
||||
case ShiftDirection::Up: return 'u';
|
||||
case ShiftDirection::Down: return 'd';
|
||||
case ShiftDirection::Right: return 'r';
|
||||
default: return 'r';
|
||||
}
|
||||
}
|
6
src/conversions.hpp
Normal file
6
src/conversions.hpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include "Hy3Node.hpp"
|
||||
|
||||
Axis getAxis(Hy3GroupLayout);
|
||||
Axis getAxis(ShiftDirection);
|
||||
SearchDirection getSearchDirection(ShiftDirection);
|
||||
char directionToChar(ShiftDirection);
|
|
@ -239,7 +239,7 @@ void dispatch_debug(std::string arg) {
|
|||
if (workspace == -1) return;
|
||||
|
||||
auto* root = g_Hy3Layout->getWorkspaceRootGroup(workspace);
|
||||
if (workspace == -1) {
|
||||
if (root == nullptr) {
|
||||
hy3_log(LOG, "DEBUG NODES: no nodes on workspace");
|
||||
} else {
|
||||
hy3_log(LOG, "DEBUG NODES\n{}", root->debugNode().c_str());
|
||||
|
|
|
@ -38,6 +38,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
CONF("node_collapse_policy", INT, 2);
|
||||
CONF("group_inset", INT, 10);
|
||||
CONF("tab_first_window", INT, 0);
|
||||
CONF("kbd_shift_delta", INT, 20);
|
||||
|
||||
// tabs
|
||||
CONF("tabs:height", INT, 15);
|
||||
|
|
Loading…
Add table
Reference in a new issue