Skip to content

Commit 376cdc9

Browse files
committed
fix(layout): wrap extent cycling when the next preset is out of reach
Fix #425
1 parent 0be184c commit 376cdc9

6 files changed

Lines changed: 69 additions & 15 deletions

File tree

‎src/layout/dwindle.cpp‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -689,16 +689,14 @@ namespace umbriel {
689689
}
690690

691691
bool DwindleLayout::cycleWidth(int columnIndex, int direction) {
692-
Node* node = nodeAtFlatIndex(columnIndex);
693-
const std::vector<Split> axisSplits = splits(node, Node::HSplit);
694-
if (axisSplits.empty()) {
695-
return false;
696-
}
697-
double current = 1.0;
698-
for (const Split& split : axisSplits) {
699-
current *= splitShare(split);
700-
}
701-
return applyFraction(axisSplits, nextFractionPreset(m_config->extentPresets, current, direction));
692+
// Each step reads the splits afresh: applying a fraction can move outer splits, which the cached products miss.
693+
return cycleFractionPreset(
694+
m_config->extentPresets,
695+
widthFraction(columnIndex),
696+
direction,
697+
[&](double fraction) { return setWidthFraction(columnIndex, fraction); },
698+
[&] { return widthFraction(columnIndex); }
699+
);
702700
}
703701

704702
bool DwindleLayout::toggleFullWidth(int columnIndex) {

‎src/layout/layout.h‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,27 @@ namespace umbriel {
205205
return presets.front();
206206
}
207207

208+
// Steps a layout extent to the next preset. `apply` sets a fraction, clamped to what the layout can realize, and
209+
// returns false when the extent is not adjustable; `realized` reads the fraction back. A preset past that range
210+
// leaves the extent where it was and would be picked again on every step, so a step that does not move the extent
211+
// wraps instead.
212+
template <typename Apply, typename Realized>
213+
bool cycleFractionPreset(
214+
const std::vector<double>& presets, double current, int direction, Apply&& apply, Realized&& realized
215+
) {
216+
const double next = nextFractionPreset(presets, current, direction);
217+
if (!apply(next)) {
218+
return false;
219+
}
220+
const bool stepped = direction < 0 ? next < current : next > current;
221+
const double now = realized();
222+
const bool stalled = direction < 0 ? now >= current - kFractionEpsilon : now <= current + kFractionEpsilon;
223+
if (stepped && stalled) {
224+
apply(direction < 0 ? presets.back() : presets.front());
225+
}
226+
return true;
227+
}
228+
208229
// Layout-owned interactive resize session. Cursor feeds a pointer delta; the session mutates only its layout's
209230
// geometry state (split ratios, width fractions, row weights). Protocol calls and arrange() stay in Cursor.
210231
struct ResizeGrab {

‎src/layout/master.cpp‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -915,8 +915,13 @@ namespace umbriel {
915915
if (!widthAdjustable() || visualArea(columnIndex) == nullptr) {
916916
return false;
917917
}
918-
const double next = nextFractionPreset(m_config->extentPresets, widthFraction(columnIndex), direction);
919-
return setWidthFraction(columnIndex, next);
918+
return cycleFractionPreset(
919+
m_config->extentPresets,
920+
widthFraction(columnIndex),
921+
direction,
922+
[&](double fraction) { return setWidthFraction(columnIndex, fraction); },
923+
[&] { return widthFraction(columnIndex); }
924+
);
920925
}
921926

922927
bool MasterStackLayout::toggleFullWidth(int columnIndex) {

‎src/workspace/workspace.cpp‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,9 +1639,12 @@ namespace umbriel {
16391639
if (m_focusedView != nullptr && m_focusedView->maximizedToEdges()) {
16401640
m_focusedView->setMaximizedToEdges(false);
16411641
}
1642-
const double current = m_layout->heightFraction(m_focusedView);
1643-
if (!m_layout->setHeightFraction(
1644-
m_focusedView, nextFractionPreset(m_layoutConfig.extentPresets, current, direction)
1642+
if (!cycleFractionPreset(
1643+
m_layoutConfig.extentPresets,
1644+
m_layout->heightFraction(m_focusedView),
1645+
direction,
1646+
[&](double fraction) { return m_layout->setHeightFraction(m_focusedView, fraction); },
1647+
[&] { return m_layout->heightFraction(m_focusedView); }
16451648
)) {
16461649
return false;
16471650
}

‎tests/unit/dwindle_layout.cpp‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,19 @@ UMBRIEL_TEST(cycleWidthBackWalksThePresetsInReverse) {
817817
CHECK(std::abs(fixture.layout.targetBox(stub(0)).width - static_cast<int>(span * (2.0 / 3.0))) <= 2);
818818
}
819819

820+
UMBRIEL_TEST(cycleWidthWrapsWhenTheNextPresetIsOutOfReach) {
821+
// A split share tops out at 0.9, short of the 1.0 preset.
822+
Fixture fixture;
823+
fixture.config.extentPresets = {0.5, 1.0};
824+
fixture.addLeaves(2);
825+
fixture.layout.arrange(kUsable);
826+
CHECK(fixture.layout.setWidthFraction(0, 0.5));
827+
CHECK(fixture.layout.cycleWidth(0, 1));
828+
CHECK(std::abs(fixture.layout.widthFraction(0) - 0.9) < 1e-9);
829+
CHECK(fixture.layout.cycleWidth(0, 1));
830+
CHECK(std::abs(fixture.layout.widthFraction(0) - 0.5) < 1e-9);
831+
}
832+
820833
UMBRIEL_TEST(resizeEdgesComeFromTileThirds) {
821834
Fixture fixture;
822835
fixture.addLeaves(2);

‎tests/unit/master_layout.cpp‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,20 @@ UMBRIEL_TEST(cycleWidthWalksThePresets) {
255255
CHECK(std::abs(fixture.layout.widthFraction(0) - 1.0 / 3.0) < 1e-9);
256256
}
257257

258+
UMBRIEL_TEST(cycleWidthWrapsWhenTheNextPresetIsOutOfReach) {
259+
// A centered side column tops out at 0.45, short of the 0.5 preset.
260+
Fixture fixture;
261+
fixture.config.master.position = umbriel::MasterPosition::Center;
262+
fixture.addViews(3);
263+
CHECK(std::abs(fixture.layout.widthFraction(0) - 0.225) < 1e-9);
264+
CHECK(fixture.layout.cycleWidth(0, 1));
265+
CHECK(std::abs(fixture.layout.widthFraction(0) - 1.0 / 3.0) < 1e-9);
266+
CHECK(fixture.layout.cycleWidth(0, 1));
267+
CHECK(std::abs(fixture.layout.widthFraction(0) - 0.45) < 1e-9);
268+
CHECK(fixture.layout.cycleWidth(0, 1));
269+
CHECK(std::abs(fixture.layout.widthFraction(0) - 1.0 / 3.0) < 1e-9);
270+
}
271+
258272
UMBRIEL_TEST(toggleFullWidthExpandsAndRestores) {
259273
Fixture fixture;
260274
fixture.addViews(2);

0 commit comments

Comments
 (0)