layout/scrolling: handle fullscreen manually

Makes scroll handle fullscreen by itself, allowing for fs scrolls.
This commit is contained in:
Vaxry 2026-04-27 21:21:32 +01:00
parent 336dc6c04e
commit ab8caea9fb
No known key found for this signature in database
30 changed files with 834 additions and 165 deletions

View file

@ -176,7 +176,7 @@ TEST_CASE(scrollSwapcolWrapping) {
}
TEST_CASE(scrollWindowRule) {
OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })"));
OK(getFromSocket("/eval hl.config({ general = { layout = 'scrolling' } })"));
NLog::log("{}Testing Scrolling Width", Colors::GREEN);
@ -195,5 +195,47 @@ TEST_CASE(scrollWindowRule) {
ASSERT(Tests::windowCount(), 2);
// not the greatest test, but as long as res and gaps don't change, we good.
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 174,1036");
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 179,1036");
}
TEST_CASE(scrollFullscreen) {
OK(getFromSocket("/eval hl.config({ general = { layout = 'scrolling' } })"));
NLog::log("{}Testing Scrolling FS", Colors::GREEN);
ASSERT(!!Tests::spawnKitty("kitty_scroll_A"), true);
ASSERT(!!Tests::spawnKitty("kitty_scroll_B"), true);
ASSERT(!!Tests::spawnKitty("kitty_scroll_C"), true);
OK(getFromSocket("/dispatch hl.dsp.focus({ window = \"class:kitty_scroll_B\" })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()"));
{
auto str = getFromSocket("/activewindow");
ASSERT_CONTAINS(str, "size: 1920,1080");
ASSERT_CONTAINS(str, "class: kitty_scroll_B");
}
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = \"left\" })"));
{
auto str = getFromSocket("/activewindow");
ASSERT_CONTAINS(str, "class: kitty_scroll_A");
}
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = \"right\" })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = \"right\" })"));
{
auto str = getFromSocket("/activewindow");
ASSERT_CONTAINS(str, "class: kitty_scroll_C");
}
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = \"left\" })"));
{
auto str = getFromSocket("/activewindow");
ASSERT_CONTAINS(str, "size: 1920,1080");
ASSERT_CONTAINS(str, "class: kitty_scroll_B");
}
}

View file

@ -1462,10 +1462,10 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection
if (!PWORKSPACE)
return nullptr; // ??
return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating);
return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow->m_isFloating, pWindow, pWindow->m_isFloating);
}
PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {
PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, bool floatingPreference, PHLWINDOW ignoreWindow, bool useVectorAngles) {
if (dir == Math::DIRECTION_DEFAULT)
return nullptr;
@ -1509,71 +1509,93 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO;
};
for (auto const& w : m_windows) {
if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || !w->acceptsInput() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
continue;
auto find = [&]() {
for (auto const& w : m_windows) {
if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
continue;
if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace)
continue;
if (w->isHidden())
continue;
if (pWorkspace->m_hasFullscreenWindow && !w->isAllowedOverFullscreen())
continue;
// check if the input is blocked by anything except BELOW_FULLSCREEN
if (w->isInputBlocked(INPUT_BLOCK_ALL & (~INPUT_BLOCK_BELOW_FULLSCREEN)))
continue;
if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor)
continue;
if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace)
continue;
const auto BWINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved();
if (pWorkspace->m_hasFullscreenWindow && !w->isAllowedOverFullscreen())
continue;
const auto POSB = Vector2D(BWINDOWIDEALBB.x, BWINDOWIDEALBB.y);
const auto SIZEB = Vector2D(BWINDOWIDEALBB.width, BWINDOWIDEALBB.height);
if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor)
continue;
double intersectLength = -1;
if (w->m_isFloating != floatingPreference)
continue;
switch (dir) {
case Math::DIRECTION_LEFT:
if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x))
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
break;
case Math::DIRECTION_RIGHT:
if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x))
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
break;
case Math::DIRECTION_UP:
if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y))
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
break;
case Math::DIRECTION_DOWN:
if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y))
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
break;
default: break;
}
const auto BWINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved();
if (*PMETHOD == 0 /* history */) {
if (intersectLength > 0) {
const auto POSB = Vector2D(BWINDOWIDEALBB.x, BWINDOWIDEALBB.y);
const auto SIZEB = Vector2D(BWINDOWIDEALBB.width, BWINDOWIDEALBB.height);
// get idx
int windowIDX = -1;
const auto& HISTORY = Desktop::History::windowTracker()->fullHistory();
for (int64_t i = HISTORY.size() - 1; i >= 0; --i) {
if (HISTORY[i] == w) {
windowIDX = i;
break;
double intersectLength = -1;
switch (dir) {
case Math::DIRECTION_LEFT:
if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x))
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
break;
case Math::DIRECTION_RIGHT:
if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x))
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
break;
case Math::DIRECTION_UP:
if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y))
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
break;
case Math::DIRECTION_DOWN:
if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y))
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
break;
default: break;
}
if (*PMETHOD == 0 /* history */) {
if (intersectLength > 0) {
// get idx
int windowIDX = -1;
const auto& HISTORY = Desktop::History::windowTracker()->fullHistory();
for (int64_t i = HISTORY.size() - 1; i >= 0; --i) {
if (HISTORY[i] == w) {
windowIDX = i;
break;
}
}
if (windowIDX > leaderValue) {
leaderValue = windowIDX;
leaderWindow = w;
}
}
if (windowIDX > leaderValue) {
leaderValue = windowIDX;
} else /* length */ {
if (intersectLength > leaderValue) {
leaderValue = intersectLength;
leaderWindow = w;
}
}
} else /* length */ {
if (intersectLength > leaderValue) {
leaderValue = intersectLength;
leaderWindow = w;
}
}
};
// Find the window, then if we don't find one with preferred
// float status, try the opposite.
find();
if (!leaderWindow) {
floatingPreference = !floatingPreference;
find();
}
} else {
static const std::unordered_map<Math::eDirection, Vector2D> VECTORS = {
{Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}};
@ -2248,9 +2270,16 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie
PWORKSPACE->m_fullscreenMode = NEW_EFFECTIVE_MODE;
PWORKSPACE->m_hasFullscreenWindow = NEW_EFFECTIVE_MODE != FSMODE_NONE;
g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), OLD_EFFECTIVE_MODE, NEW_EFFECTIVE_MODE);
PWORKSPACE->setNoMembersAboveFullscreen();
PWINDOW->m_fullscreenState.internal = state.internal;
const auto FULLSCREEN_REQUEST_RESULT = g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), OLD_EFFECTIVE_MODE, NEW_EFFECTIVE_MODE);
const bool LAYOUT_HANDLED_FULLSCREEN = FULLSCREEN_REQUEST_RESULT == Layout::FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT;
if (LAYOUT_HANDLED_FULLSCREEN) {
PWORKSPACE->m_fullscreenMode = FSMODE_NONE;
PWORKSPACE->m_hasFullscreenWindow = false;
} else
PWINDOW->m_fullscreenState.internal = state.internal;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc<int>(NEW_EFFECTIVE_MODE) != FSMODE_NONE)});
Event::bus()->m_events.window.fullscreen.emit(PWINDOW);
@ -2275,8 +2304,9 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie
ls->m_aboveFullscreen = false;
}
g_pDesktopAnimationManager->setFullscreenFadeAnimation(
PWORKSPACE, PWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);
if (!LAYOUT_HANDLED_FULLSCREEN)
g_pDesktopAnimationManager->setFullscreenFadeAnimation(
PWORKSPACE, PWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);
PWINDOW->sendWindowSize(true);
@ -2290,7 +2320,7 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie
// send a scanout tranche if we are entering fullscreen, and send a regular one if we aren't.
// ignore if DS is disabled.
if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME)) {
if (!LAYOUT_HANDLED_FULLSCREEN && (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME))) {
auto surf = PWINDOW->getSolitaryResource();
if (surf)
g_pHyprRenderer->setSurfaceScanoutMode(surf, NEW_EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr);

View file

@ -116,7 +116,8 @@ class CCompositor {
void changeWindowZOrder(PHLWINDOW, bool);
void cleanupFadingOut(const MONITORID& monid);
PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection);
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, bool floatingPreference, PHLWINDOW ignoreWindow = nullptr,
bool useVectorAngles = false);
PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false,
bool allowFullscreenBlocked = false);
PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false,

View file

@ -409,7 +409,7 @@ ActionResult Actions::moveFocus(Math::eDirection dir) {
}
const auto PWINDOWCANDIDATE = g_pCompositor->getWindowInDirection(box, PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace,
dir, PLASTWINDOW, PLASTWINDOW->m_isFloating);
dir, PLASTWINDOW->m_isFloating, PLASTWINDOW, PLASTWINDOW->m_isFloating);
if (PWINDOWCANDIDATE)
switchToWindow(PWINDOWCANDIDATE);

View file

@ -1,5 +1,6 @@
#include "Workspace.hpp"
#include "view/Group.hpp"
#include "view/LayerSurface.hpp"
#include "../Compositor.hpp"
#include "../config/shared/animation/AnimationTree.hpp"
#include "../config/shared/workspace/WorkspaceRuleManager.hpp"
@ -8,6 +9,7 @@
#include "../managers/EventManager.hpp"
#include "../helpers/Monitor.hpp"
#include "../layout/space/Space.hpp"
#include "../layout/target/Target.hpp"
#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include "../event/EventBus.hpp"
@ -539,7 +541,10 @@ void CWorkspace::rename(const std::string& name) {
}
void CWorkspace::updateWindows() {
m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; });
m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t && t->fullscreenMode() != FSMODE_NONE && !t->layoutManagedFullscreen(); });
if (!m_hasFullscreenWindow)
m_fullscreenMode = FSMODE_NONE;
for (auto const& t : m_space->targets()) {
if (t->window())
@ -562,3 +567,15 @@ void CWorkspace::setPersistent(bool persistent) {
bool CWorkspace::isPersistent() {
return m_persistent;
}
void CWorkspace::setNoMembersAboveFullscreen() {
// make all windows and layers on the same workspace under the fullscreen window
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == m_self && !w->isFullscreen() && !w->m_fadingOut && !w->m_pinned)
w->m_createdOverFullscreen = false;
}
for (auto const& ls : g_pCompositor->m_layers) {
if (ls->m_monitor == m_monitor)
ls->m_aboveFullscreen = false;
}
}

View file

@ -80,6 +80,7 @@ class CWorkspace {
void updateWindows();
void setPersistent(bool persistent);
bool isPersistent();
void setNoMembersAboveFullscreen();
struct {
CSignalT<> destroy;

View file

@ -95,6 +95,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLS
static auto PFOLLOWMOUSE = CConfigValue<Config::INTEGER>("input:follow_mouse");
static auto PSPECIALFALLTHROUGH = CConfigValue<Config::INTEGER>("input:special_fallthrough");
if (pWindow == m_focusWindow && surface == m_focusSurface)
return;
if (!pWindow || !pWindow->priorityFocus()) {
if (g_pSessionLockManager->isSessionLocked()) {
Log::logger->log(Log::DEBUG, "Refusing a keyboard focus to a window because of a sessionlock");

View file

@ -48,6 +48,7 @@
#include "../../managers/input/InputManager.hpp"
#include "../../managers/PointerManager.hpp"
#include "../../managers/animation/DesktopAnimationManager.hpp"
#include "../../layout/algorithm/Algorithm.hpp"
#include "../../layout/space/Space.hpp"
#include "../../layout/LayoutManager.hpp"
#include "../../layout/target/WindowTarget.hpp"
@ -267,7 +268,7 @@ CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
auto POS = m_position;
auto SIZE = m_size;
if (isFullscreen()) {
if (isFullscreen() && (!layoutTarget() || !layoutTarget()->layoutManagedFullscreen())) {
POS = PMONITOR->m_position;
SIZE = PMONITOR->m_size;
@ -748,12 +749,12 @@ bool CWindow::isInputBlocked() const {
return m_inputBlockReasons != INPUT_BLOCK_NONE;
}
bool CWindow::isInputBlocked(eWindowInputBlockReason reason) const {
return (m_inputBlockReasons & sc<uint32_t>(reason)) != 0;
bool CWindow::isInputBlocked(std::underlying_type_t<eWindowInputBlockReason> reasons) const {
return (m_inputBlockReasons & reasons) != 0;
}
bool CWindow::isInputBlockedOnly(eWindowInputBlockReason reason) const {
return m_inputBlockReasons == sc<uint32_t>(reason);
return m_inputBlockReasons == reason;
}
bool CWindow::acceptsInput() const {
@ -772,7 +773,13 @@ bool CWindow::isAllowedOverFullscreen() const {
}
bool CWindow::isBlockedByFullscreen() const {
if (!m_workspace || !m_workspace->m_hasFullscreenWindow)
if (!m_workspace)
return false;
const auto ALGORITHM = m_workspace->m_space ? m_workspace->m_space->algorithm() : nullptr;
const bool HAS_LAYOUT_FULLSCREEN = ALGORITHM && ALGORITHM->layoutFullscreenCoversMonitor();
if (!m_workspace->m_hasFullscreenWindow && !HAS_LAYOUT_FULLSCREEN)
return false;
return !isAllowedOverFullscreen();

View file

@ -89,11 +89,13 @@ namespace Desktop::View {
WINDOW_ALPHA_LAST,
};
enum eWindowInputBlockReason : uint32_t {
enum eWindowInputBlockReason : uint8_t {
INPUT_BLOCK_NONE = 0,
INPUT_BLOCK_GROUP_INACTIVE = 1 << 0,
INPUT_BLOCK_MONOCLE_INACTIVE = 1 << 1,
INPUT_BLOCK_BELOW_FULLSCREEN = 1 << 2,
INPUT_BLOCK_GROUP_INACTIVE = (1 << 0),
INPUT_BLOCK_MONOCLE_INACTIVE = (1 << 1),
INPUT_BLOCK_BELOW_FULLSCREEN = (1 << 2),
INPUT_BLOCK_ALL = std::numeric_limits<std::underlying_type_t<eWindowInputBlockReason>>::max(),
};
struct SWindowActiveEvent {
@ -285,6 +287,11 @@ namespace Desktop::View {
// For the noclosefor windowrule
Time::steady_tp m_closeableSince = Time::steadyNow();
// layout-settable flags. These are reset when layout changes.
struct {
bool cantLockCursor = false;
} m_layoutFlags;
// For the list lookup
bool operator==(const CWindow& rhs) const {
return m_xdgSurface == rhs.m_xdgSurface && m_xwaylandSurface == rhs.m_xwaylandSurface && m_position == rhs.m_position && m_size == rhs.m_size &&
@ -314,7 +321,7 @@ namespace Desktop::View {
bool isHidden() const;
void setInputBlocked(eWindowInputBlockReason reason, bool blocked);
bool isInputBlocked() const;
bool isInputBlocked(eWindowInputBlockReason reason) const;
bool isInputBlocked(std::underlying_type_t<eWindowInputBlockReason> reasons) const;
bool isInputBlockedOnly(eWindowInputBlockReason reason) const;
bool acceptsInput() const;
bool isAllowedOverFullscreen() const;

View file

@ -30,6 +30,8 @@
#include "../managers/input/InputManager.hpp"
#include "../errorOverlay/Overlay.hpp"
#include "../layout/LayoutManager.hpp"
#include "../layout/space/Space.hpp"
#include "../layout/algorithm/Algorithm.hpp"
#include "../i18n/Engine.hpp"
#include "../helpers/cm/ColorManagement.hpp"
#include "time/Time.hpp"
@ -1672,7 +1674,7 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) {
return reasons;
}
if (!PWORKSPACE->m_hasFullscreenWindow) {
if (!inFullscreenMode()) {
reasons |= SC_WINDOWED;
if (!full)
return reasons;
@ -1720,7 +1722,7 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) {
return reasons;
}
const auto PCANDIDATE = PWORKSPACE->getFullscreenWindow();
const auto PCANDIDATE = getFullscreenWindow();
if (!PCANDIDATE) {
reasons |= SC_CANDIDATE;
@ -1791,7 +1793,7 @@ void CMonitor::recheckSolitary() {
if (isSolitaryBlocked())
return;
m_solitaryClient = PWORKSPACE->getFullscreenWindow();
m_solitaryClient = getFullscreenWindow();
}
uint8_t CMonitor::isTearingBlocked(bool full) {
@ -1871,11 +1873,12 @@ uint16_t CMonitor::isDSBlocked(bool full) {
}
if (*PDIRECTSCANOUT == 2) {
if (!PWORKSPACE || !PWORKSPACE->m_hasFullscreenWindow || PWORKSPACE->m_fullscreenMode != FSMODE_FULLSCREEN) {
const auto FSWINDOW = getFullscreenWindow();
if (!PWORKSPACE || !inFullscreenMode() || !FSWINDOW) {
reasons |= DS_BLOCK_WINDOWED;
if (!full)
return reasons;
} else if (PWORKSPACE->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) {
} else if (FSWINDOW->getContentType() != CONTENT_TYPE_GAME) {
reasons |= DS_BLOCK_CONTENT;
if (!full)
return reasons;
@ -2268,17 +2271,34 @@ bool CMonitor::inHDR() {
bool CMonitor::inFullscreenMode() {
// Check special workspace first since it renders on top of regular workspaces
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
if (m_activeSpecialWorkspace &&
((m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) ||
(m_activeSpecialWorkspace->m_space && m_activeSpecialWorkspace->m_space->algorithm() && m_activeSpecialWorkspace->m_space->algorithm()->layoutFullscreenCoversMonitor())))
return true;
return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN;
return m_activeWorkspace &&
((m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) ||
(m_activeWorkspace->m_space && m_activeWorkspace->m_space->algorithm() && m_activeWorkspace->m_space->algorithm()->layoutFullscreenCoversMonitor()));
}
PHLWINDOW CMonitor::getFullscreenWindow() {
// Check special workspace first since it renders on top of regular workspaces
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
return m_activeSpecialWorkspace->getFullscreenWindow();
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_space && m_activeSpecialWorkspace->m_space->algorithm() &&
m_activeSpecialWorkspace->m_space->algorithm()->layoutFullscreenCoversMonitor()) {
const auto TARGET = m_activeSpecialWorkspace->m_space->algorithm()->layoutFullscreenTarget();
return TARGET ? TARGET->window() : nullptr;
}
if (m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
return m_activeWorkspace->getFullscreenWindow();
if (m_activeWorkspace && m_activeWorkspace->m_space && m_activeWorkspace->m_space->algorithm() && m_activeWorkspace->m_space->algorithm()->layoutFullscreenCoversMonitor()) {
const auto TARGET = m_activeWorkspace->m_space->algorithm()->layoutFullscreenTarget();
return TARGET ? TARGET->window() : nullptr;
}
return nullptr;
}

View file

@ -94,9 +94,11 @@ void CLayoutManager::endDragTarget() {
m_dragStateController->dragEnd();
}
void CLayoutManager::fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) {
if (target->space())
target->space()->setFullscreen(target, effectiveMode);
eFullscreenRequestResult CLayoutManager::fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) {
if (target && target->space())
return target->space()->setFullscreen(target, currentEffectiveMode, effectiveMode);
return FULLSCREEN_REQUEST_DEFAULT;
}
void CLayoutManager::switchTargets(SP<ITarget> a, SP<ITarget> b, bool preserveFocus) {

View file

@ -40,43 +40,48 @@ namespace Layout {
SNAP_RIGHT = (1 << 3),
};
enum eFullscreenRequestResult : uint8_t {
FULLSCREEN_REQUEST_DEFAULT = 0,
FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT,
};
class CLayoutManager {
public:
CLayoutManager();
~CLayoutManager() = default;
void newTarget(SP<ITarget> target, SP<CSpace> space);
void removeTarget(SP<ITarget> target);
void newTarget(SP<ITarget> target, SP<CSpace> space);
void removeTarget(SP<ITarget> target);
void changeFloatingMode(SP<ITarget> target);
void changeFloatingMode(SP<ITarget> target);
void beginDragTarget(SP<ITarget> target, eMouseBindMode mode);
void moveMouse(const Vector2D& mousePos);
void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
void moveTarget(const Vector2D& Δ, SP<ITarget> target);
void setTargetGeom(const CBox& box, SP<ITarget> target); // floats only
void endDragTarget();
void beginDragTarget(SP<ITarget> target, eMouseBindMode mode);
void moveMouse(const Vector2D& mousePos);
void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
void moveTarget(const Vector2D& Δ, SP<ITarget> target);
void setTargetGeom(const CBox& box, SP<ITarget> target); // floats only
void endDragTarget();
Config::ErrorResult layoutMsg(const std::string_view& sv);
Config::ErrorResult layoutMsg(const std::string_view& sv);
void fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode);
eFullscreenRequestResult fullscreenRequestForTarget(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode);
void switchTargets(SP<ITarget> a, SP<ITarget> b, bool preserveFocus = true);
void switchTargets(SP<ITarget> a, SP<ITarget> b, bool preserveFocus = true);
void moveInDirection(SP<ITarget> target, const std::string& direction, bool silent = false);
void moveInDirection(SP<ITarget> target, const std::string& direction, bool silent = false);
SP<ITarget> getNextCandidate(SP<CSpace> space, SP<ITarget> from);
SP<ITarget> getNextCandidate(SP<CSpace> space, SP<ITarget> from);
bool isReachable(SP<ITarget> target);
bool isReachable(SP<ITarget> target);
void bringTargetToTop(SP<ITarget> target);
void bringTargetToTop(SP<ITarget> target);
std::optional<Vector2D> predictSizeForNewTiledTarget();
std::optional<Vector2D> predictSizeForNewTiledTarget();
void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP<ITarget> target, eMouseBindMode mode, int corner, const Vector2D& beginSize);
void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP<ITarget> target, eMouseBindMode mode, int corner, const Vector2D& beginSize);
void invalidateMonitorGeometries(PHLMONITOR);
void recalculateMonitor(PHLMONITOR);
void invalidateMonitorGeometries(PHLMONITOR);
void recalculateMonitor(PHLMONITOR);
const UP<Supplementary::CDragStateController>& dragController();

View file

@ -154,6 +154,25 @@ void CAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) {
m_floating->moveTarget(Δ, target);
}
eFullscreenRequestResult CAlgorithm::requestFullscreen(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) {
if (!target)
return FULLSCREEN_REQUEST_DEFAULT;
const SFullscreenRequest request = {.target = target, .currentEffectiveMode = currentEffectiveMode, .effectiveMode = effectiveMode};
return target->floating() ? m_floating->requestFullscreen(request) : m_tiled->requestFullscreen(request);
}
SP<ITarget> CAlgorithm::layoutFullscreenTarget() const {
if (const auto TARGET = m_tiled->layoutFullscreenTarget(); TARGET)
return TARGET;
return m_floating->layoutFullscreenTarget();
}
bool CAlgorithm::layoutFullscreenCoversMonitor() const {
return m_tiled->layoutFullscreenCoversMonitor() || m_floating->layoutFullscreenCoversMonitor();
}
void CAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
auto swapFirst = [&a, &b](std::vector<WP<ITarget>>& targets) -> bool {
auto ia = std::ranges::find(targets, a);

View file

@ -42,6 +42,10 @@ namespace Layout {
void setTargetGeom(const CBox& box, SP<ITarget> target); // only for float
eFullscreenRequestResult requestFullscreen(SP<ITarget> target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode);
SP<ITarget> layoutFullscreenTarget() const;
bool layoutFullscreenCoversMonitor() const;
void updateFloatingAlgo(UP<IFloatingAlgorithm>&& algo);
void updateTiledAlgo(UP<ITiledAlgorithm>&& algo);

View file

@ -15,6 +15,19 @@ std::optional<Vector2D> IModeAlgorithm::predictSizeForNewTarget() {
return std::nullopt;
}
eFullscreenRequestResult IModeAlgorithm::requestFullscreen(const SFullscreenRequest& request) {
(void)request;
return FULLSCREEN_REQUEST_DEFAULT;
}
SP<ITarget> IModeAlgorithm::layoutFullscreenTarget() const {
return nullptr;
}
bool IModeAlgorithm::layoutFullscreenCoversMonitor() const {
return false;
}
std::optional<Vector2D> IModeAlgorithm::focalPointForDir(SP<ITarget> t, Math::eDirection dir) {
Vector2D focalPoint;

View file

@ -13,6 +13,12 @@ namespace Layout {
class ITarget;
class CAlgorithm;
struct SFullscreenRequest {
SP<ITarget> target;
eFullscreenMode currentEffectiveMode = static_cast<eFullscreenMode>(0);
eFullscreenMode effectiveMode = static_cast<eFullscreenMode>(0);
};
class IModeAlgorithm {
public:
virtual ~IModeAlgorithm() = default;
@ -44,6 +50,13 @@ namespace Layout {
// optional: predict new window's size
virtual std::optional<Vector2D> predictSizeForNewTarget();
// optional: allow algorithms to own fullscreen semantics for a target.
virtual eFullscreenRequestResult requestFullscreen(const SFullscreenRequest& request);
// optional: expose an algorithm-owned fullscreen target and whether it is monitor-exclusive.
virtual SP<ITarget> layoutFullscreenTarget() const;
virtual bool layoutFullscreenCoversMonitor() const;
// Impl'd here: focal point for dir
virtual std::optional<Vector2D> focalPointForDir(SP<ITarget> t, Math::eDirection dir);

View file

@ -147,14 +147,10 @@ double CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox&
return usablePrimary * m_strips[stripIndex].size;
}
CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) {
CBox CScrollTapeController::calculateStripBox(size_t stripIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) {
if (stripIndex >= m_strips.size())
return {};
const auto& strip = m_strips[stripIndex];
if (targetIndex >= strip.targetSizes.size())
return {};
const double usableSecondary = getSecondary(usableArea.size());
const double usablePrimary = getPrimary(usableArea.size());
const double cameraOffset = calculateCameraOffset(usableArea, fullscreenOnOne);
@ -163,13 +159,6 @@ CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetI
double primaryPos = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
double primarySize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
// calculate position along secondary axis (within strip)
double secondaryPos = 0.0;
for (size_t i = 0; i < targetIndex; ++i) {
secondaryPos += strip.targetSizes[i] * usableSecondary;
}
double secondarySize = strip.targetSizes[targetIndex] * usableSecondary;
// apply camera offset based on direction
// for RIGHT/DOWN: scroll offset moves content left/up (subtract)
// for LEFT/UP: scroll offset moves content right/down (different coordinate system)
@ -185,8 +174,8 @@ CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetI
}
// create the box in primary/secondary coordinates
Vector2D pos = makeVector(primaryPos, secondaryPos);
Vector2D size = makeVector(primarySize, secondarySize);
Vector2D pos = makeVector(primaryPos, 0.0);
Vector2D size = makeVector(primarySize, usableSecondary);
// translate to workspace position
pos = pos + workspaceOffset;
@ -194,6 +183,35 @@ CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetI
return CBox{pos, size};
}
CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) {
if (stripIndex >= m_strips.size())
return {};
const auto& strip = m_strips[stripIndex];
if (targetIndex >= strip.targetSizes.size())
return {};
CBox stripBox = calculateStripBox(stripIndex, usableArea, workspaceOffset, fullscreenOnOne);
const double usableSecondary = getSecondary(usableArea.size());
double secondaryPos = 0.0;
for (size_t i = 0; i < targetIndex; ++i) {
secondaryPos += strip.targetSizes[i] * usableSecondary;
}
const double secondarySize = strip.targetSizes[targetIndex] * usableSecondary;
if (isPrimaryHorizontal()) {
stripBox.y = workspaceOffset.y + secondaryPos;
stripBox.h = secondarySize;
} else {
stripBox.x = workspaceOffset.x + secondaryPos;
stripBox.w = secondarySize;
}
return stripBox;
}
double CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) {
const double maxExtent = calculateMaxExtent(usableArea, fullscreenOnOne);
const double usablePrimary = getPrimary(usableArea.size());
@ -275,13 +293,14 @@ size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool full
return 0;
const double usablePrimary = getPrimary(usableArea.size());
double currentPos = m_offset;
const double viewCenter = m_offset + usablePrimary / 2.0;
double currentEnd = 0.0;
for (size_t i = 0; i < m_strips.size(); ++i) {
const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne);
currentPos += stripSize;
currentEnd += stripSize;
if (currentPos >= usablePrimary / 2.0 - 2.0)
if (currentEnd >= viewCenter - 2.0)
return i;
}

View file

@ -55,6 +55,7 @@ namespace Layout::Tiled {
double calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
double calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
CBox calculateStripBox(size_t stripIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false);
CBox calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false);
double calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false);

View file

@ -11,6 +11,7 @@
#include "../../../../config/shared/workspace/WorkspaceRuleManager.hpp"
#include "../../../../render/Renderer.hpp"
#include "../../../../managers/input/InputManager.hpp"
#include "../../../../managers/animation/DesktopAnimationManager.hpp"
#include "../../../../event/EventBus.hpp"
#include <hyprutils/string/VarList2.hpp>
@ -427,23 +428,83 @@ void SScrollingData::recalculate(bool forceInstant) {
algorithm->m_parent->space()->workspace()->m_hasFullscreenWindow)
return;
algorithm->syncFullscreenTargets();
static const auto PFSONONE = CConfigValue<Config::INTEGER>("scrolling:fullscreen_on_one_column");
const CBox USABLE = algorithm->usableArea();
const auto WORKAREA = algorithm->m_parent->space()->workArea();
const CBox MONBOX = algorithm->m_parent->space()->workspace()->m_monitor->logicalBox();
const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(algorithm->m_parent->space()->workspace());
static auto PGAPSINDATA = CConfigValue<Config::IComplexConfigValue>("general:gaps_in");
auto* const PGAPSIN = sc<Config::CCssGapData*>((PGAPSINDATA.ptr()));
const auto GAPSIN = (WORKSPACERULE && WORKSPACERULE->m_gapsIn.has_value()) ? WORKSPACERULE->m_gapsIn.value() : *PGAPSIN;
bool anyFullscreenCovers = false;
for (const auto& COL : columns) {
if (algorithm->fullscreenTargetDataForColumn(COL) && algorithm->fullscreenColumnCoversMonitor(COL)) {
anyFullscreenCovers = true;
break;
}
}
controller->setDirection(algorithm->getDynamicDirection());
algorithm->updateFullscreenFade(anyFullscreenCovers);
const auto targetBoxWithGaps = [&](const CBox& logical, size_t colIdx, size_t targetIdx, bool fullscreenOrHidden) -> STargetBox {
if (fullscreenOrHidden)
return {.logicalBox = logical, .visualBox = logical};
CBox visual = logical;
const bool PRIMARY_HORIZ = controller->isPrimaryHorizontal();
const bool GAP_LEFT = PRIMARY_HORIZ ? colIdx > 0 : targetIdx > 0;
const bool GAP_RIGHT = PRIMARY_HORIZ ? colIdx + 1 < columns.size() : targetIdx + 1 < columns[colIdx]->targetDatas.size();
const bool GAP_TOP = PRIMARY_HORIZ ? targetIdx > 0 : colIdx > 0;
const bool GAP_BOTTOM = PRIMARY_HORIZ ? targetIdx + 1 < columns[colIdx]->targetDatas.size() : colIdx + 1 < columns.size();
const auto GAPOFFSETTOPLEFT = Vector2D(sc<double>(GAP_LEFT ? GAPSIN.m_left : 0), sc<double>(GAP_TOP ? GAPSIN.m_top : 0));
const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc<double>(GAP_RIGHT ? GAPSIN.m_right : 0), sc<double>(GAP_BOTTOM ? GAPSIN.m_bottom : 0));
visual.x += GAPOFFSETTOPLEFT.x;
visual.y += GAPOFFSETTOPLEFT.y;
visual.w = std::max(1.0, visual.w - GAPOFFSETTOPLEFT.x - GAPOFFSETBOTTOMRIGHT.x);
visual.h = std::max(1.0, visual.h - GAPOFFSETTOPLEFT.y - GAPOFFSETBOTTOMRIGHT.y);
return {.logicalBox = logical, .visualBox = visual};
};
for (size_t i = 0; i < columns.size(); ++i) {
const auto& COL = columns[i];
const auto FS = algorithm->fullscreenTargetDataForColumn(COL);
for (size_t j = 0; j < COL->targetDatas.size(); ++j) {
const auto& TARGET = COL->targetDatas[j];
TARGET->layoutBox = controller->calculateTargetBox(i, j, USABLE, WORKAREA.pos(), *PFSONONE);
if (FS) {
if (TARGET == FS) {
if (algorithm->fullscreenColumnCoversMonitor(COL))
TARGET->layoutBox = MONBOX;
else {
TARGET->layoutBox = controller->calculateStripBox(i, USABLE, WORKAREA.pos(), *PFSONONE);
if (controller->isPrimaryHorizontal()) {
TARGET->layoutBox.y = MONBOX.y;
TARGET->layoutBox.h = MONBOX.h;
} else {
TARGET->layoutBox.x = MONBOX.x;
TARGET->layoutBox.w = MONBOX.w;
}
}
} else
TARGET->layoutBox = CBox{WORKAREA.pos() - Vector2D{100000.0, 100000.0}, Vector2D{1.0, 1.0}};
} else
TARGET->layoutBox = controller->calculateTargetBox(i, j, USABLE, WORKAREA.pos(), *PFSONONE);
if (TARGET->target)
TARGET->target->setPositionGlobal(TARGET->layoutBox);
TARGET->target->setPositionGlobal(targetBoxWithGaps(TARGET->layoutBox, i, j, FS));
if (forceInstant && TARGET->target)
TARGET->target->warpPositionSize();
}
@ -544,6 +605,9 @@ CScrollingAlgorithm::CScrollingAlgorithm() {
}
CScrollingAlgorithm::~CScrollingAlgorithm() {
clearFullscreenTarget();
updateFullscreenFade(false);
m_configCallback.reset();
m_focusCallback.reset();
}
@ -631,6 +695,8 @@ void CScrollingAlgorithm::removeTarget(SP<ITarget> target) {
if (!DATA)
return;
clearFullscreenTarget(target);
if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) {
// move the view if this is the last column
const auto USABLE = usableArea();
@ -814,6 +880,303 @@ void CScrollingAlgorithm::recalculate() {
m_scrollingData->recalculate();
}
void CScrollingAlgorithm::syncFullscreenTargets() {
for (auto it = m_fullscreenTargets.begin(); it != m_fullscreenTargets.end();) {
const auto TARGET = it->target.lock();
if (!TARGET || !TARGET->layoutManagedFullscreen() || TARGET->fullscreenMode() != FSMODE_FULLSCREEN || TARGET->space() != m_parent->space()) {
it = m_fullscreenTargets.erase(it);
continue;
}
const auto TDATA = dataFor(TARGET);
if (!TDATA) {
++it;
continue;
}
if (const auto COL = TDATA->column.lock())
COL->setColumnWidth(fullscreenColumnWidth());
++it;
}
for (const auto& COL : m_scrollingData->columns) {
for (const auto& TDATA : COL->targetDatas) {
const auto TARGET = TDATA->target.lock();
if (!TARGET || !TARGET->layoutManagedFullscreen() || TARGET->fullscreenMode() != FSMODE_FULLSCREEN || TARGET->space() != m_parent->space())
continue;
if (!fullscreenStateForTarget(TARGET))
m_fullscreenTargets.emplace_back(SFullscreenScrollState{.target = TARGET, .restoreColumnWidth = COL ? std::optional<float>{COL->getColumnWidth()} : std::nullopt});
COL->setColumnWidth(fullscreenColumnWidth());
}
}
}
CScrollingAlgorithm::SFullscreenScrollState* CScrollingAlgorithm::fullscreenStateForTarget(SP<ITarget> target) {
if (!target)
return nullptr;
for (auto& state : m_fullscreenTargets) {
if (state.target.lock() == target)
return &state;
}
return nullptr;
}
CScrollingAlgorithm::SFullscreenScrollState* CScrollingAlgorithm::fullscreenStateForData(SP<SScrollingTargetData> target) {
if (!target)
return nullptr;
return fullscreenStateForTarget(target->target.lock());
}
void CScrollingAlgorithm::expelTarget(SP<SScrollingTargetData> tdata, SP<SColumnData> srcCol, std::optional<int64_t> insertIdx) {
auto col = !insertIdx ? m_scrollingData->add() : m_scrollingData->add(*insertIdx);
srcCol->remove(tdata->target.lock());
col->add(tdata);
m_scrollingData->centerOrFitCol(col);
}
eFullscreenRequestResult CScrollingAlgorithm::requestFullscreen(const SFullscreenRequest& request) {
if (!request.target || !m_parent || request.target->space() != m_parent->space())
return FULLSCREEN_REQUEST_DEFAULT;
const auto TDATA = dataFor(request.target);
if (!TDATA)
return FULLSCREEN_REQUEST_DEFAULT;
if (request.effectiveMode == FSMODE_FULLSCREEN) {
if (!fullscreenStateForTarget(request.target)) {
const auto COL = TDATA->column.lock();
m_fullscreenTargets.emplace_back(
SFullscreenScrollState{.target = request.target, .restoreColumnWidth = COL ? std::optional<float>{COL->getColumnWidth()} : std::nullopt});
}
if (const auto COL = TDATA->column.lock()) {
COL->setColumnWidth(fullscreenColumnWidth());
m_scrollingData->centerOrFitCol(COL);
}
request.target->setFullscreenMode(FSMODE_FULLSCREEN);
return FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT;
} else if (request.effectiveMode == FSMODE_MAXIMIZED) {
// expel, then max width
const auto CURRENT_COL = TDATA->column.lock();
if (CURRENT_COL->targetDatas.size() > 1) {
const auto lastTarget = CURRENT_COL->targetDatas.back();
const auto currentIdx = m_scrollingData->idx(CURRENT_COL);
const auto NEXT_COL = m_scrollingData->next(CURRENT_COL);
const auto insertIdx = !NEXT_COL ? std::nullopt : std::optional<int64_t>{currentIdx};
expelTarget(lastTarget, CURRENT_COL, insertIdx);
TDATA->column->setColumnWidth(1.F);
} else
CURRENT_COL->setColumnWidth(1.F);
request.target->setFullscreenMode(FSMODE_NONE);
return FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT;
}
if (isFullscreenTarget(TDATA) || request.target->layoutManagedFullscreen()) {
clearFullscreenTarget(request.target);
request.target->setFullscreenMode(FSMODE_NONE);
return request.effectiveMode == FSMODE_NONE ? FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT : FULLSCREEN_REQUEST_DEFAULT;
}
return FULLSCREEN_REQUEST_DEFAULT;
}
SP<ITarget> CScrollingAlgorithm::layoutFullscreenTarget() const {
SP<SScrollingTargetData> fallback;
for (const auto& COL : m_scrollingData->columns) {
for (const auto& TDATA : COL->targetDatas) {
if (!isFullscreenTarget(TDATA))
continue;
if (!fallback)
fallback = TDATA;
if (fullscreenColumnCoversMonitor(TDATA->column.lock()))
return TDATA->target.lock();
}
}
return fallback ? fallback->target.lock() : nullptr;
}
bool CScrollingAlgorithm::layoutFullscreenCoversMonitor() const {
for (const auto& COL : m_scrollingData->columns) {
for (const auto& TDATA : COL->targetDatas) {
if (!isFullscreenTarget(TDATA))
continue;
if (fullscreenColumnCoversMonitor(TDATA->column.lock()))
return true;
}
}
return false;
}
SP<SScrollingTargetData> CScrollingAlgorithm::fullscreenTargetDataForColumn(SP<SColumnData> col) const {
if (!col)
return nullptr;
for (const auto& TDATA : col->targetDatas) {
if (!isFullscreenTarget(TDATA))
continue;
return TDATA;
}
return nullptr;
}
bool CScrollingAlgorithm::isFullscreenTarget(SP<SScrollingTargetData> target) const {
if (!target)
return false;
const auto TARGET = target->target.lock();
if (!TARGET || !TARGET->layoutManagedFullscreen() || TARGET->fullscreenMode() == FSMODE_NONE)
return false;
return dataFor(TARGET) == target;
}
float CScrollingAlgorithm::fullscreenColumnWidth() const {
if (!m_parent || !m_parent->space() || !m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor || !m_scrollingData || !m_scrollingData->controller)
return 1.F;
const auto USABLE = usableArea();
const auto MONBOX = m_parent->space()->workspace()->m_monitor->logicalBox();
const bool PRIMARY_HORIZ = m_scrollingData->controller->isPrimaryHorizontal();
const double usablePrimary = PRIMARY_HORIZ ? USABLE.w : USABLE.h;
const double monitorPrimary = PRIMARY_HORIZ ? MONBOX.w : MONBOX.h;
if (usablePrimary <= 0.0)
return 1.F;
return std::max(1.F, sc<float>(monitorPrimary / usablePrimary));
}
bool CScrollingAlgorithm::fullscreenColumnCoversMonitor(SP<SColumnData> col) const {
if (!col || !m_scrollingData || !m_scrollingData->controller || !m_parent || !m_parent->space() || !m_parent->space()->workspace() ||
!m_parent->space()->workspace()->m_monitor)
return false;
if (!fullscreenTargetDataForColumn(col))
return false;
const int64_t COL_IDX = m_scrollingData->idx(col);
if (COL_IDX < 0)
return false;
static const auto PFSONONE = CConfigValue<Config::INTEGER>("scrolling:fullscreen_on_one_column");
const auto USABLE = usableArea();
const bool PRIMARY_HORIZ = m_scrollingData->controller->isPrimaryHorizontal();
const double VIEW_SIZE = PRIMARY_HORIZ ? USABLE.w : USABLE.h;
const double VIEW_START = m_scrollingData->controller->getOffset();
const double VIEW_END = VIEW_START + VIEW_SIZE;
const double COL_START = m_scrollingData->controller->calculateStripStart(COL_IDX, USABLE, *PFSONONE);
const double COL_END = COL_START + m_scrollingData->controller->calculateStripSize(COL_IDX, USABLE, *PFSONONE);
return COL_START <= VIEW_START + 1.0 && COL_END >= VIEW_END - 1.0;
}
void CScrollingAlgorithm::updateFullscreenFade(bool coversMonitor) {
if (m_lastFullscreenCover == coversMonitor)
return;
m_lastFullscreenCover = coversMonitor;
if (!coversMonitor) {
// prevent stuck focus
g_pInputManager->unconstrainMouse();
for (const auto& fs : m_fullscreenTargets) {
if (!fs.target || !fs.target->window())
continue;
auto w = fs.target->window();
w->m_layoutFlags.cantLockCursor = true;
}
} else {
for (const auto& fs : m_fullscreenTargets) {
if (!fs.target || !fs.target->window())
continue;
auto w = fs.target->window();
w->m_layoutFlags.cantLockCursor = false;
}
}
if (!m_parent || !m_parent->space() || !m_parent->space()->workspace())
return;
// properly update things on top / bottom
m_parent->space()->workspace()->setNoMembersAboveFullscreen();
g_pDesktopAnimationManager->setFullscreenFadeAnimation(m_parent->space()->workspace(),
coversMonitor ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);
}
void CScrollingAlgorithm::clearFullscreenTarget(SP<ITarget> target) {
bool cleared = false;
auto clear = [&](SP<ITarget> t) {
t->setLayoutManagedFullscreen(false);
if (t->window())
t->window()->m_layoutFlags.cantLockCursor = false;
cleared = true;
};
for (auto it = m_fullscreenTargets.begin(); it != m_fullscreenTargets.end();) {
const auto TARGET = it->target.lock();
if (!TARGET || (target && TARGET != target)) {
if (!TARGET)
it = m_fullscreenTargets.erase(it);
else
++it;
continue;
}
const auto TDATA = dataFor(TARGET);
clear(TARGET);
if (const auto COL = TDATA ? TDATA->column.lock() : nullptr; COL && it->restoreColumnWidth)
COL->setColumnWidth(*it->restoreColumnWidth);
it = m_fullscreenTargets.erase(it);
}
if (target && target->layoutManagedFullscreen())
clear(target);
else if (!target) {
for (const auto& COL : m_scrollingData->columns) {
for (const auto& TDATA : COL->targetDatas) {
const auto TARGET = TDATA->target.lock();
if (!TARGET || !TARGET->layoutManagedFullscreen())
continue;
clear(TARGET);
}
}
}
}
SP<SScrollingTargetData> CScrollingAlgorithm::closestNode(const Vector2D& posGlobglobgabgalab) {
SP<SScrollingTargetData> res = nullptr;
double distClosest = -1;
@ -1372,14 +1735,6 @@ Config::ErrorResult CScrollingAlgorithm::layoutMsg(const std::string_view& sv) {
if (!CURRENT_COL)
return stateErr("no current col");
// expel a target from srcCol into its own new column at insertIdx
auto expelTarget = [&](SP<SScrollingTargetData> tdata, SP<SColumnData> srcCol, std::optional<int64_t> insertIdx) {
auto col = !insertIdx ? m_scrollingData->add() : m_scrollingData->add(*insertIdx);
srcCol->remove(tdata->target.lock());
col->add(tdata);
m_scrollingData->centerOrFitCol(col);
};
// consume the first target from adjCol into dstCol
auto consumeTarget = [&](SP<SColumnData> dstCol, SP<SColumnData> adjCol) {
const auto target = adjCol->targetDatas.front();
@ -1546,7 +1901,7 @@ SP<SScrollingTargetData> CScrollingAlgorithm::findBestNeighbor(SP<SScrollingTarg
return nullptr;
}
SP<SScrollingTargetData> CScrollingAlgorithm::dataFor(SP<ITarget> t) {
SP<SScrollingTargetData> CScrollingAlgorithm::dataFor(SP<ITarget> t) const {
if (!t)
return nullptr;
@ -1584,7 +1939,7 @@ eScrollDirection CScrollingAlgorithm::getDynamicDirection() {
return SCROLL_DIR_RIGHT; // default
}
CBox CScrollingAlgorithm::usableArea() {
CBox CScrollingAlgorithm::usableArea() const {
if (!m_parent || !m_parent->space())
return {};

View file

@ -5,6 +5,7 @@
#include "ScrollTapeController.hpp"
#include "../../../../helpers/signal/Signal.hpp"
#include <optional>
#include <vector>
namespace Layout::Tiled {
@ -94,23 +95,27 @@ namespace Layout::Tiled {
CScrollingAlgorithm();
virtual ~CScrollingAlgorithm();
virtual void newTarget(SP<ITarget> target);
virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
virtual void removeTarget(SP<ITarget> target);
virtual void newTarget(SP<ITarget> target);
virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
virtual void removeTarget(SP<ITarget> target);
virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
virtual void recalculate();
virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
virtual void recalculate();
virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
virtual Config::ErrorResult layoutMsg(const std::string_view& sv);
virtual std::optional<Vector2D> predictSizeForNewTarget();
virtual Config::ErrorResult layoutMsg(const std::string_view& sv);
virtual std::optional<Vector2D> predictSizeForNewTarget();
virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
CBox usableArea();
SP<SScrollingTargetData> dataFor(SP<ITarget> t);
virtual eFullscreenRequestResult requestFullscreen(const SFullscreenRequest& request);
virtual SP<ITarget> layoutFullscreenTarget() const;
virtual bool layoutFullscreenCoversMonitor() const;
CBox usableArea() const;
SP<SScrollingTargetData> dataFor(SP<ITarget> t) const;
enum eInputMode : uint8_t {
INPUT_MODE_SOFT = 0,
@ -129,16 +134,36 @@ namespace Layout::Tiled {
std::vector<float> configuredWidths;
} m_config;
eScrollDirection getDynamicDirection();
eScrollDirection getDynamicDirection();
SP<SScrollingTargetData> findBestNeighbor(SP<SScrollingTargetData> pCurrent, SP<SColumnData> pTargetCol);
SP<SScrollingTargetData> closestNode(const Vector2D& posGlobglobgabgalab);
struct SFullscreenScrollState {
WP<ITarget> target;
std::optional<float> restoreColumnWidth;
};
void focusTargetUpdate(SP<ITarget> target);
void moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent);
void focusOnInput(SP<ITarget> target, eInputMode input);
void syncFullscreenTargets();
SFullscreenScrollState* fullscreenStateForTarget(SP<ITarget> target);
SFullscreenScrollState* fullscreenStateForData(SP<SScrollingTargetData> target);
SP<SScrollingTargetData> fullscreenTargetDataForColumn(SP<SColumnData> col) const;
bool isFullscreenTarget(SP<SScrollingTargetData> target) const;
float fullscreenColumnWidth() const;
bool fullscreenColumnCoversMonitor(SP<SColumnData> col) const;
void updateFullscreenFade(bool coversMonitor);
void clearFullscreenTarget(SP<ITarget> target = nullptr);
float defaultColumnWidth();
SP<SScrollingTargetData> findBestNeighbor(SP<SScrollingTargetData> pCurrent, SP<SColumnData> pTargetCol);
SP<SScrollingTargetData> closestNode(const Vector2D& posGlobglobgabgalab);
void focusTargetUpdate(SP<ITarget> target);
void moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent);
void focusOnInput(SP<ITarget> target, eInputMode input);
void expelTarget(SP<SScrollingTargetData> tdata, SP<SColumnData> srcCol, std::optional<int64_t> insertIdx);
float defaultColumnWidth();
std::vector<SFullscreenScrollState> m_fullscreenTargets;
bool m_lastFullscreenCover = false;
friend struct SScrollingData;
};

View file

@ -153,13 +153,29 @@ void CSpace::recalculate() {
m_algorithm->recalculate();
}
void CSpace::setFullscreen(SP<ITarget> t, eFullscreenMode mode) {
t->setFullscreenMode(mode);
eFullscreenRequestResult CSpace::setFullscreen(SP<ITarget> t, eFullscreenMode currentEffectiveMode, eFullscreenMode mode) {
if (!t)
return FULLSCREEN_REQUEST_DEFAULT;
const auto REQUEST_RESULT = m_algorithm ? m_algorithm->requestFullscreen(t, currentEffectiveMode, mode) : FULLSCREEN_REQUEST_DEFAULT;
t->setLayoutManagedFullscreen(REQUEST_RESULT == FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT && mode == FSMODE_FULLSCREEN);
if (REQUEST_RESULT != FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT)
t->setFullscreenMode(mode);
if (REQUEST_RESULT == FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT) {
if (const auto WORKSPACE = workspace()) {
WORKSPACE->m_fullscreenMode = FSMODE_NONE;
WORKSPACE->m_hasFullscreenWindow = false;
}
}
if (mode == FSMODE_NONE && m_algorithm && t->floating())
m_algorithm->recenter(t);
recalculate();
return REQUEST_RESULT;
}
Config::ErrorResult CSpace::layoutMsg(const std::string_view& sv) {

View file

@ -30,7 +30,7 @@ namespace Layout {
void setAlgorithmProvider(SP<CAlgorithm> algo);
void recheckWorkArea();
void setFullscreen(SP<ITarget> t, eFullscreenMode mode);
eFullscreenRequestResult setFullscreen(SP<ITarget> t, eFullscreenMode currentEffectiveMode, eFullscreenMode mode);
void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);

View file

@ -50,6 +50,14 @@ void ITarget::setSpaceGhost(const SP<CSpace>& space) {
m_ghostSpace = true;
}
bool ITarget::layoutManagedFullscreen() const {
return m_layoutManagedFullscreen;
}
void ITarget::setLayoutManagedFullscreen(bool enabled) {
m_layoutManagedFullscreen = enabled;
}
SP<CSpace> ITarget::space() const {
return m_space;
}

View file

@ -57,6 +57,8 @@ namespace Layout {
virtual void setPseudoSize(const Vector2D& size);
virtual Vector2D pseudoSize();
virtual void swap(SP<ITarget> b);
virtual bool layoutManagedFullscreen() const;
virtual void setLayoutManagedFullscreen(bool enabled);
//
virtual bool floating() = 0;
@ -77,9 +79,10 @@ namespace Layout {
SP<CSpace> m_space;
WP<ITarget> m_self;
Vector2D m_floatingSize;
bool m_pseudo = false;
bool m_ghostSpace = false; // ghost space means a target belongs to a space, but isn't sent to the layout
Vector2D m_pseudoSize = {1280, 720};
bool m_wasTiling = false;
bool m_pseudo = false;
bool m_ghostSpace = false; // ghost space means a target belongs to a space, but isn't sent to the layout
Vector2D m_pseudoSize = {1280, 720};
bool m_wasTiling = false;
bool m_layoutManagedFullscreen = false;
};
};

View file

@ -68,6 +68,18 @@ void CWindowGroupTarget::setFullscreenMode(eFullscreenMode mode) {
m_group->current()->m_fullscreenState.internal = mode;
}
bool CWindowGroupTarget::layoutManagedFullscreen() const {
return m_group->current()->m_target->layoutManagedFullscreen();
}
void CWindowGroupTarget::setLayoutManagedFullscreen(bool enabled) {
ITarget::setLayoutManagedFullscreen(enabled);
for (const auto& w : m_group->windows()) {
w->m_target->setLayoutManagedFullscreen(enabled);
}
}
std::optional<Vector2D> CWindowGroupTarget::minSize() {
return m_group->current()->minSize();
}

View file

@ -28,6 +28,8 @@ namespace Layout {
virtual void damageEntire();
virtual void warpPositionSize();
virtual void onUpdateSpace();
virtual bool layoutManagedFullscreen() const;
virtual void setLayoutManagedFullscreen(bool enabled);
private:
CWindowGroupTarget(SP<Desktop::View::CGroup> g);

View file

@ -43,7 +43,7 @@ void CWindowTarget::updatePos() {
if (!m_space)
return;
if (fullscreenMode() == FSMODE_FULLSCREEN)
if (fullscreenMode() == FSMODE_FULLSCREEN && !layoutManagedFullscreen())
return;
if (floating() && fullscreenMode() != FSMODE_MAXIMIZED) {
@ -83,7 +83,24 @@ void CWindowTarget::updatePos() {
return;
}
if (fullscreenMode() == FSMODE_FULLSCREEN)
if (fullscreenMode() == FSMODE_FULLSCREEN && layoutManagedFullscreen()) {
CBox nodeBox = m_box.logicalBox;
CBox visualBox = m_box.visualBox.empty() ? nodeBox : m_box.visualBox;
nodeBox.round();
visualBox.round();
m_window->m_size = nodeBox.size();
m_window->m_position = nodeBox.pos();
*m_window->m_realSize = visualBox.size();
*m_window->m_realPosition = visualBox.pos();
m_window->updateWindowDecos();
m_window->sendWindowSize();
return;
}
if (fullscreenMode() == FSMODE_FULLSCREEN && !layoutManagedFullscreen())
return;
g_pHyprRenderer->damageWindow(window());
@ -206,6 +223,7 @@ void CWindowTarget::updatePos() {
}
m_window->updateWindowDecos();
m_window->sendWindowSize();
}
void CWindowTarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint) {
@ -214,6 +232,8 @@ void CWindowTarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2
return;
}
m_window->m_layoutFlags = {};
// keep the ref here so that moveToWorkspace doesn't unref the workspace
// and assignToSpace doesn't think this is a new target because space wp is dead
const auto WSREF = space->workspace();
@ -236,6 +256,8 @@ void CWindowTarget::setFloating(bool x) {
if (x == m_window->m_isFloating)
return;
m_window->m_layoutFlags = {};
m_window->m_isFloating = x;
m_window->m_pinned = false;

View file

@ -487,7 +487,7 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim
if (ws->m_id == PMONITOR->activeWorkspaceID() || ws->m_id == PMONITOR->activeSpecialWorkspaceID()) {
for (auto const& ls : PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {
if (!ls->m_fadingOut && !ls->m_aboveFullscreen)
*ls->m_alpha = FULLSCREEN && ws->m_fullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f;
*ls->m_alpha = FULLSCREEN && ws->m_fullscreenMode != FSMODE_MAXIMIZED ? 0.f : 1.f;
}
}
}

View file

@ -453,7 +453,9 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
return pWindowIdeal;
};
if (PWORKSPACE->m_hasFullscreenWindow && PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) {
const bool HAS_EXCLUSIVE_FULLSCREEN = (PWORKSPACE->m_hasFullscreenWindow && PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) || PMONITOR->inFullscreenMode();
if (HAS_EXCLUSIVE_FULLSCREEN) {
const auto IS_LS_UNFOCUSABLE = pFoundLayerSurface &&
(pFoundLayerSurface->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP ||
(pFoundLayerSurface->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP && !pFoundLayerSurface->m_aboveFullscreen));
@ -1741,7 +1743,17 @@ void CInputManager::unconstrainMouse() {
bool CInputManager::isConstrained() {
return std::ranges::any_of(m_constraints, [](auto const& c) {
const auto constraint = c.lock();
return constraint && constraint->isActive() && constraint->owner()->resource() == Desktop::focusState()->surface();
if (!constraint || !constraint->isActive() || constraint->owner()->resource() != Desktop::focusState()->surface())
return false;
const auto OWNER = constraint->owner()->view();
const auto WINDOW = Desktop::View::CWindow::fromView(OWNER);
if (!WINDOW)
return false;
return !WINDOW->m_layoutFlags.cantLockCursor;
});
}
@ -1749,7 +1761,15 @@ bool CInputManager::isLocked() {
if (!isConstrained())
return false;
const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface());
const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface());
if (SURF) {
const auto WINDOW = Desktop::View::CWindow::fromView(SURF->view());
if (WINDOW && WINDOW->m_layoutFlags.cantLockCursor)
return false;
}
const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr;
return CONSTRAINT && CONSTRAINT->isLocked();

View file

@ -127,9 +127,11 @@ void CPointerConstraint::activate() {
// TODO: hack, probably not a super duper great idea
if (g_pSeatManager->m_state.pointerFocus != m_hlSurface->resource()) {
const auto SURFBOX = m_hlSurface->getSurfaceBoxGlobal();
const auto LOCAL = SURFBOX.has_value() ? logicPositionHint() - SURFBOX->pos() : Vector2D{};
g_pSeatManager->setPointerFocus(m_hlSurface->resource(), LOCAL);
if (const auto W = Desktop::View::CWindow::fromView(m_hlSurface->view()); !W || !W->m_layoutFlags.cantLockCursor) {
const auto SURFBOX = m_hlSurface->getSurfaceBoxGlobal();
const auto LOCAL = SURFBOX.has_value() ? logicPositionHint() - SURFBOX->pos() : Vector2D{};
g_pSeatManager->setPointerFocus(m_hlSurface->resource(), LOCAL);
}
}
if (m_locked)