Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/user/keybinds.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ These take no argument.
| `window-consume-left` | Pull the focused window into the column to its left. |
| `window-expel-right` | Pop the focused window out of its column into a new column to the right. |
| `window-cycle-width` | Cycle the focused column through its preset widths. |
| `window-cycle-width-back` | Cycle the focused column through its preset widths in reverse. |
| `window-toggle-fullscreen` | Toggle fullscreen for the focused window. |
| `window-toggle-maximize` | Toggle the focused column's full-width state. |
| `window-toggle-maximize-to-edges` | Toggle maximization of the focused window to the usable area's edges, without gaps or borders. Layer-shell exclusive zones remain visible. |
Expand Down
2 changes: 2 additions & 0 deletions src/config/keybind_parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ namespace umbriel {
{"window-close", "[<window-id>]", KeybindAction::WindowClose, ActionArgKind::OptionalWindowId},
{"window-consume-left", "", KeybindAction::WindowConsumeLeft},
{"window-cycle-width", "", KeybindAction::WindowCycleWidth},
{"window-cycle-width-back", "", KeybindAction::WindowCycleWidthBack},
{"window-expel-right", "", KeybindAction::WindowExpelRight},
{"window-focus", "<window-id>", KeybindAction::WindowFocusId, ActionArgKind::WindowId},
{"window-focus-down", "", KeybindAction::WindowFocusDown},
Expand Down Expand Up @@ -459,6 +460,7 @@ namespace umbriel {
add(KeybindAction::WindowConsumeLeft, XKB_KEY_comma);
add(KeybindAction::WindowExpelRight, XKB_KEY_period);
add(KeybindAction::WindowCycleWidth, XKB_KEY_r);
add(KeybindAction::WindowCycleWidthBack, XKB_KEY_r, WLR_MODIFIER_SHIFT);
add(KeybindAction::ToggleFullscreen, XKB_KEY_f);
add(KeybindAction::ToggleMaximize, XKB_KEY_f, WLR_MODIFIER_CTRL);
add(KeybindAction::ToggleMaximizeToEdges, XKB_KEY_m);
Expand Down
1 change: 1 addition & 0 deletions src/config/keybind_parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ namespace umbriel {
WindowConsumeLeft,
WindowExpelRight,
WindowCycleWidth,
WindowCycleWidthBack,
WindowSetWidth,
ToggleMaximize,
ToggleMaximizeToEdges,
Expand Down
14 changes: 11 additions & 3 deletions src/layout/dwindle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ namespace umbriel {
return verticalSibling(view, direction);
}

bool DwindleLayout::cycleWidth(int columnIndex) {
bool DwindleLayout::cycleWidth(int columnIndex, int direction) {
Node* node = nodeAtFlatIndex(columnIndex);
const std::vector<WidthSplit> splits = widthSplits(node);
if (splits.empty()) {
Expand All @@ -505,8 +505,16 @@ namespace umbriel {
current *= widthShare(split);
}
const auto& presets = m_config->widthPresets;
const auto it = std::ranges::find_if(presets, [current](double preset) { return preset > current + 0.0001; });
const double next = (it == presets.end()) ? presets[0] : *it;
double next = 0.0;
if (direction < 0) {
const auto it = std::ranges::find_if(presets | std::views::reverse, [current](double preset) {
return preset < current - 0.0001;
});
next = it == presets.rend() ? presets.back() : *it;
} else {
const auto it = std::ranges::find_if(presets, [current](double preset) { return preset > current + 0.0001; });
next = it == presets.end() ? presets[0] : *it;
}
return applyWidthFraction(splits, next);
}

Expand Down
2 changes: 1 addition & 1 deletion src/layout/dwindle.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ namespace umbriel {
[[nodiscard]] uint32_t sanitizeResizeEdges(const View* view, uint32_t edges) const override;
std::unique_ptr<ResizeGrab> beginResize(View* view, uint32_t edges, const wlr_box& usable) override;

bool cycleWidth(int columnIndex) override;
bool cycleWidth(int columnIndex, int direction) override;
bool toggleFullWidth(int columnIndex) override;
[[nodiscard]] bool isFullWidth(int columnIndex) const override;
bool setWidthFraction(int columnIndex, double fraction) override;
Expand Down
2 changes: 1 addition & 1 deletion src/layout/layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ namespace umbriel {

[[nodiscard]] virtual View* focusVerticalLeaf(const View* /*view*/, int /*direction*/) const { return nullptr; }

virtual bool cycleWidth(int columnIndex) = 0;
virtual bool cycleWidth(int columnIndex, int direction) = 0;
virtual bool toggleFullWidth(int columnIndex) = 0;
virtual bool setWidthFraction(int columnIndex, double fraction) = 0;
virtual void clearFullWidthState(int columnIndex) = 0;
Expand Down
17 changes: 12 additions & 5 deletions src/layout/scrolling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <algorithm>
#include <cmath>
#include <ranges>

// wlr_box and WLR_EDGE_* only. Layout geometry must not pull src/wlr.h, which
// drags SceneFX and the renderer into a translation unit that does arithmetic.
Expand Down Expand Up @@ -440,16 +441,22 @@ namespace umbriel {
return {.x = it->x, .y = it->y, .width = it->width, .height = it->height};
}

bool ScrollingLayout::cycleWidth(int columnIndex) {
bool ScrollingLayout::cycleWidth(int columnIndex, int direction) {
if (columnIndex < 0 || columnIndex >= static_cast<int>(m_columns.size())) {
return false;
}
Column& column = m_columns[static_cast<size_t>(columnIndex)];
const auto& presets = m_config->widthPresets;
const auto it = std::ranges::find_if(presets, [current = column.widthFrac](double preset) {
return preset > current + 0.0001;
});
column.widthFrac = it == presets.end() ? presets[0] : *it;
const double current = column.widthFrac;
if (direction < 0) {
const auto it = std::ranges::find_if(presets | std::views::reverse, [current](double preset) {
return preset < current - 0.0001;
});
column.widthFrac = it == presets.rend() ? presets.back() : *it;
} else {
const auto it = std::ranges::find_if(presets, [current](double preset) { return preset > current + 0.0001; });
column.widthFrac = it == presets.end() ? presets[0] : *it;
}
column.savedWidthFrac = 0.0;
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/layout/scrolling.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ namespace umbriel {
[[nodiscard]] InitialSize
initialSize(const wlr_box& usable, std::optional<double> ruleWidthFraction) const override;

bool cycleWidth(int columnIndex) override;
bool cycleWidth(int columnIndex, int direction) override;
bool toggleFullWidth(int columnIndex) override;
bool setWidthFraction(int columnIndex, double fraction) override;
void clearFullWidthState(int columnIndex) override;
Expand Down
1 change: 1 addition & 0 deletions src/scene/cheatsheet_rows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ namespace {
case A::WindowConsumeLeft:
case A::WindowExpelRight:
case A::WindowCycleWidth:
case A::WindowCycleWidthBack:
case A::WindowSetWidth:
case A::WindowModifyWidth:
case A::WindowCenter:
Expand Down
7 changes: 4 additions & 3 deletions src/server/actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,9 @@ namespace umbriel {
return true;
}

bool actionCycleWidth(Server& server, const Keybind& /*bind*/, std::string* /*error*/) {
template <int Direction> bool actionCycleWidth(Server& server, const Keybind& /*bind*/, std::string* /*error*/) {
if (Workspace* workspace = activeWorkspace(server)) {
workspace->cycleFocusedWidth();
workspace->cycleFocusedWidth(Direction);
}
return true;
}
Expand Down Expand Up @@ -845,7 +845,8 @@ namespace umbriel {
&actionMoveVertical<1>,
&actionConsumeLeft,
&actionExpelRight,
&actionCycleWidth,
&actionCycleWidth<1>,
&actionCycleWidth<-1>,
&actionSetWidth,
&actionToggleMaximize,
&actionToggleMaximizeToEdges,
Expand Down
4 changes: 2 additions & 2 deletions src/workspace/workspace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,12 +638,12 @@ namespace umbriel {
return true;
}

bool Workspace::cycleFocusedWidth() {
bool Workspace::cycleFocusedWidth(int direction) {
if (m_focusedView != nullptr && m_focusedView->maximizedToEdges()) {
m_focusedView->setMaximizedToEdges(false);
}
const int column = m_layout->columnOf(m_focusedView);
if (!m_layout->cycleWidth(column)) {
if (!m_layout->cycleWidth(column, direction)) {
return false;
}
wlr_xdg_toplevel_set_maximized(m_focusedView->toplevel(), false);
Expand Down
2 changes: 1 addition & 1 deletion src/workspace/workspace.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ namespace umbriel {
bool consumeFocusedLeft();
bool expelFocusedRight();
bool moveFocusedVertical(int direction);
bool cycleFocusedWidth();
bool cycleFocusedWidth(int direction);
bool setFocusedWidth(double fraction);
// Incremental width change: apply `delta` to the focused column's current
// width fraction, clamped to [0.1, 1.0].
Expand Down
24 changes: 24 additions & 0 deletions tests/dwindle_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,30 @@ UMBRIEL_TEST(resizeBoundaryRejectsAScreenFacingEdge) {
CHECK(!fixture.layout.setResizeBoundary(stub(0), WLR_EDGE_LEFT, 0.75));
}

UMBRIEL_TEST(cycleWidthBackWalksThePresetsInReverse) {
Fixture fixture;
fixture.config.widthPresets = {1.0 / 3.0, 0.5, 2.0 / 3.0};
fixture.addLeaves(2);
fixture.layout.arrange(kUsable);

const int span = kUsable.width - 2 * fixture.config.edgePad - fixture.config.totalGap;

CHECK(fixture.layout.setWidthFraction(0, 2.0 / 3.0));

CHECK(fixture.layout.cycleWidth(0, -1));
fixture.layout.arrange(kUsable);
CHECK(std::abs(fixture.layout.targetBox(stub(0)).width - static_cast<int>(span * 0.5)) <= 2);

CHECK(fixture.layout.cycleWidth(0, -1));
fixture.layout.arrange(kUsable);
CHECK(std::abs(fixture.layout.targetBox(stub(0)).width - static_cast<int>(span * (1.0 / 3.0))) <= 2);

// Wraps back to the last preset.
CHECK(fixture.layout.cycleWidth(0, -1));
fixture.layout.arrange(kUsable);
CHECK(std::abs(fixture.layout.targetBox(stub(0)).width - static_cast<int>(span * (2.0 / 3.0))) <= 2);
}

// The scrolling-only API is no longer reachable from a DwindleLayout at all: scroll offsets, column positions, and row
// weights moved onto ScrollingLayout, so the question a previous test asked here ("does dwindle answer 0?") cannot be
// compiled any more. That is the point. Callers reach those through Workspace::scrollingLayout(), which is null for a
Expand Down
22 changes: 19 additions & 3 deletions tests/scrolling_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,18 +296,34 @@ UMBRIEL_TEST(cycleWidthWalksThePresets) {
fixture.addColumns(1);
CHECK(fixture.layout.setWidthFraction(0, 1.0 / 3));

CHECK(fixture.layout.cycleWidth(0));
CHECK(fixture.layout.cycleWidth(0, 1));
const double second = fixture.layout.widthFraction(0);
CHECK(std::fabs(second - 0.5) < 1e-6);

CHECK(fixture.layout.cycleWidth(0));
CHECK(fixture.layout.cycleWidth(0, 1));
CHECK(std::fabs(fixture.layout.widthFraction(0) - 2.0 / 3) < 1e-6);

// Wraps back to the first preset.
CHECK(fixture.layout.cycleWidth(0));
CHECK(fixture.layout.cycleWidth(0, 1));
CHECK(std::fabs(fixture.layout.widthFraction(0) - 1.0 / 3) < 1e-6);
}

UMBRIEL_TEST(cycleWidthBackWalksThePresetsInReverse) {
Fixture fixture;
fixture.addColumns(1);
CHECK(fixture.layout.setWidthFraction(0, 2.0 / 3));

CHECK(fixture.layout.cycleWidth(0, -1));
CHECK(std::fabs(fixture.layout.widthFraction(0) - 0.5) < 1e-6);

CHECK(fixture.layout.cycleWidth(0, -1));
CHECK(std::fabs(fixture.layout.widthFraction(0) - 1.0 / 3) < 1e-6);

// Wraps back to the last preset.
CHECK(fixture.layout.cycleWidth(0, -1));
CHECK(std::fabs(fixture.layout.widthFraction(0) - 2.0 / 3) < 1e-6);
}

UMBRIEL_TEST(toggleFullWidthRestoresThePreviousFraction) {
Fixture fixture;
fixture.addColumns(2);
Expand Down