layout: rethonk layouts from the ground up (#12890)

Rewrites layouts to be much smaller, and deal with much less annoying
BS. Improves the overall architecture, unifies handling of pseudotiling,
and various other improvements.
This commit is contained in:
Vaxry 2026-02-21 21:30:39 +00:00 committed by GitHub
parent 51f8849e54
commit 723870337f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 8431 additions and 5527 deletions

View file

@ -0,0 +1,293 @@
#include "ScrollTapeController.hpp"
#include "ScrollingAlgorithm.hpp"
#include <algorithm>
#include <cmath>
using namespace Layout::Tiled;
CScrollTapeController::CScrollTapeController(eScrollDirection direction) : m_direction(direction) {
;
}
void CScrollTapeController::setDirection(eScrollDirection dir) {
m_direction = dir;
}
eScrollDirection CScrollTapeController::getDirection() const {
return m_direction;
}
bool CScrollTapeController::isPrimaryHorizontal() const {
return m_direction == SCROLL_DIR_RIGHT || m_direction == SCROLL_DIR_LEFT;
}
bool CScrollTapeController::isReversed() const {
return m_direction == SCROLL_DIR_LEFT || m_direction == SCROLL_DIR_UP;
}
size_t CScrollTapeController::stripCount() const {
return m_strips.size();
}
SStripData& CScrollTapeController::getStrip(size_t index) {
return m_strips[index];
}
const SStripData& CScrollTapeController::getStrip(size_t index) const {
return m_strips[index];
}
void CScrollTapeController::setOffset(double offset) {
m_offset = offset;
}
double CScrollTapeController::getOffset() const {
return m_offset;
}
void CScrollTapeController::adjustOffset(double delta) {
m_offset += delta;
}
size_t CScrollTapeController::addStrip(float size) {
m_strips.emplace_back();
m_strips.back().size = size;
return m_strips.size() - 1;
}
void CScrollTapeController::insertStrip(size_t afterIndex, float size) {
if (afterIndex >= m_strips.size()) {
addStrip(size);
return;
}
SStripData newStrip;
newStrip.size = size;
m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip);
}
void CScrollTapeController::removeStrip(size_t index) {
if (index < m_strips.size())
m_strips.erase(m_strips.begin() + index);
}
double CScrollTapeController::getPrimary(const Vector2D& v) const {
return isPrimaryHorizontal() ? v.x : v.y;
}
double CScrollTapeController::getSecondary(const Vector2D& v) const {
return isPrimaryHorizontal() ? v.y : v.x;
}
void CScrollTapeController::setPrimary(Vector2D& v, double val) const {
if (isPrimaryHorizontal())
v.x = val;
else
v.y = val;
}
void CScrollTapeController::setSecondary(Vector2D& v, double val) const {
if (isPrimaryHorizontal())
v.y = val;
else
v.x = val;
}
Vector2D CScrollTapeController::makeVector(double primary, double secondary) const {
if (isPrimaryHorizontal())
return {primary, secondary};
else
return {secondary, primary};
}
double CScrollTapeController::calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne) const {
if (m_strips.empty())
return 0.0;
if (fullscreenOnOne && m_strips.size() == 1)
return getPrimary(usableArea.size());
double total = 0.0;
const double usablePrimary = getPrimary(usableArea.size());
for (const auto& strip : m_strips) {
total += usablePrimary * strip.size;
}
return total;
}
double CScrollTapeController::calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
if (stripIndex >= m_strips.size())
return 0.0;
const double usablePrimary = getPrimary(usableArea.size());
double current = 0.0;
for (size_t i = 0; i < stripIndex; ++i) {
const double stripSize = (fullscreenOnOne && m_strips.size() == 1) ? usablePrimary : usablePrimary * m_strips[i].size;
current += stripSize;
}
return current;
}
double CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
if (stripIndex >= m_strips.size())
return 0.0;
const double usablePrimary = getPrimary(usableArea.size());
if (fullscreenOnOne && m_strips.size() == 1)
return usablePrimary;
return usablePrimary * m_strips[stripIndex].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 {};
const double usableSecondary = getSecondary(usableArea.size());
const double usablePrimary = getPrimary(usableArea.size());
const double cameraOffset = calculateCameraOffset(usableArea, fullscreenOnOne);
// calculate position along primary axis (strip position)
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)
if (m_direction == SCROLL_DIR_LEFT) {
// LEFT: flip the entire primary axis, then apply offset
primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;
} else if (m_direction == SCROLL_DIR_UP) {
// UP: flip the entire primary axis, then apply offset
primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;
} else {
// RIGHT/DOWN: normal offset
primaryPos -= cameraOffset;
}
// create the box in primary/secondary coordinates
Vector2D pos = makeVector(primaryPos, secondaryPos);
Vector2D size = makeVector(primarySize, secondarySize);
// translate to workspace position
pos = pos + workspaceOffset;
return CBox{pos, size};
}
double CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) {
const double maxExtent = calculateMaxExtent(usableArea, fullscreenOnOne);
const double usablePrimary = getPrimary(usableArea.size());
// don't adjust the offset if we are dragging
if (isBeingDragged())
return m_offset;
// if the content fits in viewport, center it
if (maxExtent < usablePrimary)
m_offset = std::round((maxExtent - usablePrimary) / 2.0);
// if the offset is negative but we already extended, reset offset to 0
if (maxExtent > usablePrimary && m_offset < 0.0)
m_offset = 0.0;
return m_offset;
}
Vector2D CScrollTapeController::getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne) {
const double offset = calculateCameraOffset(usableArea, fullscreenOnOne);
if (isReversed())
return makeVector(offset, 0.0);
else
return makeVector(-offset, 0.0);
}
void CScrollTapeController::centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {
if (stripIndex >= m_strips.size())
return;
const double usablePrimary = getPrimary(usableArea.size());
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
m_offset = stripStart - (usablePrimary - stripSize) / 2.0;
}
void CScrollTapeController::fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {
if (stripIndex >= m_strips.size())
return;
const double usablePrimary = getPrimary(usableArea.size());
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
m_offset = std::clamp(m_offset, stripStart - usablePrimary + stripSize, stripStart);
}
bool CScrollTapeController::isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
if (stripIndex >= m_strips.size())
return false;
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
const double stripEnd = stripStart + calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
const double viewStart = m_offset;
const double viewEnd = m_offset + getPrimary(usableArea.size());
return stripStart < viewEnd && viewStart < stripEnd;
}
size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne) const {
if (m_strips.empty())
return 0;
const double usablePrimary = getPrimary(usableArea.size());
double currentPos = m_offset;
for (size_t i = 0; i < m_strips.size(); ++i) {
const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne);
currentPos += stripSize;
if (currentPos >= usablePrimary / 2.0 - 2.0)
return i;
}
return m_strips.empty() ? 0 : m_strips.size() - 1;
}
void CScrollTapeController::swapStrips(size_t a, size_t b) {
if (a >= m_strips.size() || b >= m_strips.size())
return;
std::swap(m_strips.at(a), m_strips.at(b));
}
bool CScrollTapeController::isBeingDragged() const {
for (const auto& s : m_strips) {
if (!s.userData)
continue;
for (const auto& d : s.userData->targetDatas) {
if (d->target == g_layoutManager->dragController()->target())
return true;
}
}
return false;
}

View file

@ -0,0 +1,83 @@
#pragma once
#include "../../../../helpers/math/Math.hpp"
#include "../../../../helpers/memory/Memory.hpp"
#include <vector>
namespace Layout::Tiled {
struct SColumnData;
enum eScrollDirection : uint8_t {
SCROLL_DIR_RIGHT = 0,
SCROLL_DIR_LEFT,
SCROLL_DIR_DOWN,
SCROLL_DIR_UP,
};
struct SStripData {
float size = 1.F; // size along primary axis
std::vector<float> targetSizes; // sizes along secondary axis for each target in this strip
WP<SColumnData> userData;
SStripData() = default;
};
struct STapeLayoutResult {
CBox box;
size_t stripIndex = 0;
size_t targetIndex = 0;
};
class CScrollTapeController {
public:
CScrollTapeController(eScrollDirection direction = SCROLL_DIR_RIGHT);
~CScrollTapeController() = default;
void setDirection(eScrollDirection dir);
eScrollDirection getDirection() const;
bool isPrimaryHorizontal() const;
bool isReversed() const;
size_t addStrip(float size = 1.0F);
void insertStrip(size_t afterIndex, float size = 1.0F);
void removeStrip(size_t index);
size_t stripCount() const;
SStripData& getStrip(size_t index);
const SStripData& getStrip(size_t index) const;
void swapStrips(size_t a, size_t b);
void setOffset(double offset);
double getOffset() const;
void adjustOffset(double delta);
double calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne = false) const;
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 calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false);
double calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false);
Vector2D getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne = false);
void centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false);
void fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false);
bool isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
size_t getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne = false) const;
private:
eScrollDirection m_direction = SCROLL_DIR_RIGHT;
std::vector<SStripData> m_strips;
double m_offset = 0.0;
double getPrimary(const Vector2D& v) const;
double getSecondary(const Vector2D& v) const;
void setPrimary(Vector2D& v, double val) const;
void setSecondary(Vector2D& v, double val) const;
bool isBeingDragged() const;
Vector2D makeVector(double primary, double secondary) const;
};
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,137 @@
#pragma once
#include "../../TiledAlgorithm.hpp"
#include "../../../../managers/HookSystemManager.hpp"
#include "../../../../helpers/math/Direction.hpp"
#include "ScrollTapeController.hpp"
#include <vector>
namespace Layout::Tiled {
class CScrollingAlgorithm;
struct SColumnData;
struct SScrollingData;
struct SScrollingTargetData {
SScrollingTargetData(SP<ITarget> t, SP<SColumnData> col) : target(t), column(col) {
;
}
WP<ITarget> target;
WP<SColumnData> column;
bool ignoreFullscreenChecks = false;
CBox layoutBox;
};
struct SColumnData {
SColumnData(SP<SScrollingData> data) : scrollingData(data) {
;
}
void add(SP<ITarget> t);
void add(SP<ITarget> t, int after);
void add(SP<SScrollingTargetData> w);
void add(SP<SScrollingTargetData> w, int after);
void remove(SP<ITarget> t);
bool has(SP<ITarget> t);
size_t idx(SP<ITarget> t);
// index of lowest target that is above y.
size_t idxForHeight(float y);
void up(SP<SScrollingTargetData> w);
void down(SP<SScrollingTargetData> w);
SP<SScrollingTargetData> next(SP<SScrollingTargetData> w);
SP<SScrollingTargetData> prev(SP<SScrollingTargetData> w);
std::vector<SP<SScrollingTargetData>> targetDatas;
WP<SScrollingData> scrollingData;
WP<SScrollingTargetData> lastFocusedTarget;
WP<SColumnData> self;
// Helper methods to access controller-managed data
float getColumnWidth() const;
void setColumnWidth(float width);
float getTargetSize(size_t idx) const;
void setTargetSize(size_t idx, float size);
float getTargetSize(SP<SScrollingTargetData> target) const;
void setTargetSize(SP<SScrollingTargetData> target, float size);
};
struct SScrollingData {
SScrollingData(CScrollingAlgorithm* algo);
std::vector<SP<SColumnData>> columns;
UP<CScrollTapeController> controller;
SP<SColumnData> add();
SP<SColumnData> add(int after);
int64_t idx(SP<SColumnData> c);
void remove(SP<SColumnData> c);
double maxWidth();
SP<SColumnData> next(SP<SColumnData> c);
SP<SColumnData> prev(SP<SColumnData> c);
SP<SColumnData> atCenter();
bool visible(SP<SColumnData> c);
void centerCol(SP<SColumnData> c);
void fitCol(SP<SColumnData> c);
void centerOrFitCol(SP<SColumnData> c);
void recalculate(bool forceInstant = false);
CScrollingAlgorithm* algorithm = nullptr;
WP<SScrollingData> self;
std::optional<double> lockedCameraOffset;
};
class CScrollingAlgorithm : public ITiledAlgorithm {
public:
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 resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
virtual void recalculate();
virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
virtual std::expected<void, std::string> 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);
CBox usableArea();
private:
SP<SScrollingData> m_scrollingData;
SP<HOOK_CALLBACK_FN> m_configCallback;
SP<HOOK_CALLBACK_FN> m_focusCallback;
SP<HOOK_CALLBACK_FN> m_mouseButtonCallback;
struct {
std::vector<float> configuredWidths;
} m_config;
eScrollDirection getDynamicDirection();
SP<SScrollingTargetData> findBestNeighbor(SP<SScrollingTargetData> pCurrent, SP<SColumnData> pTargetCol);
SP<SScrollingTargetData> dataFor(SP<ITarget> t);
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, bool hardInput);
friend struct SScrollingData;
};
};