diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 9a73a88..d5e4aec 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -72,6 +72,12 @@ used to control and configure river. _shell_command_ if you do not want special characters to get interpreted by your shell before the command gets passed to _/bin/sh_. +*swap* *next*|*previous* + Swap the focused window with the next/previous visible non-floating window. + When the focused view is the first view there is no previous view. + In this case *previous* swaps with the last view. + *next* behaves analogous. + *toggle-float* If the focused view is floating, make it tiled. If it is tiled, make it floating. diff --git a/river/command.zig b/river/command.zig index 92da001..257bd08 100644 --- a/river/command.zig +++ b/river/command.zig @@ -70,6 +70,7 @@ const str_to_impl_fn = [_]struct { .{ .name = "set-view-tags", .impl = @import("command/tags.zig").setViewTags }, .{ .name = "snap", .impl = @import("command/move.zig").snap }, .{ .name = "spawn", .impl = @import("command/spawn.zig").spawn }, + .{ .name = "swap", .impl = @import("command/swap.zig").swap}, .{ .name = "toggle-float", .impl = @import("command/toggle_float.zig").toggleFloat }, .{ .name = "toggle-focused-tags", .impl = @import("command/tags.zig").toggleFocusedTags }, .{ .name = "toggle-fullscreen", .impl = @import("command/toggle_fullscreen.zig").toggleFullscreen }, diff --git a/river/command/swap.zig b/river/command/swap.zig new file mode 100644 index 0000000..093f318 --- /dev/null +++ b/river/command/swap.zig @@ -0,0 +1,83 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2020 Marten Ringwelski +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +const std = @import("std"); + +const Error = @import("../command.zig").Error; +const Direction = @import("../command.zig").Direction; +const Seat = @import("../Seat.zig"); +const View = @import("../View.zig"); +const ViewStack = @import("../view_stack.zig").ViewStack; + +/// Swap the currently focused view with either the view higher or lower in the visible stack +pub fn swap( + allocator: *std.mem.Allocator, + seat: *Seat, + args: []const []const u8, + out: *?[]const u8, +) Error!void { + if (args.len < 2) return Error.NotEnoughArguments; + if (args.len > 2) return Error.TooManyArguments; + + if (seat.focused != .view) + return; + + // Filter out everything that is not part of the current layout + if (seat.focused.view.pending.float or seat.focused.view.pending.fullscreen) return; + + const direction = std.meta.stringToEnum(Direction, args[1]) orelse return Error.InvalidDirection; + + const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view); + const output = seat.focused_output; + var it = ViewStack(View).iter( + focused_node, + if (direction == .next) .forward else .reverse, + output.pending.tags, + filter, + ); + var it_wrap = ViewStack(View).iter( + if (direction == .next) output.views.first else output.views.last, + .forward, + output.pending.tags, + filter, + ); + + // skip the first node which is focused_node + _ = it.next().?; + + const to_swap = @fieldParentPtr( + ViewStack(View).Node, + "view", + // Wrap around if needed + if (it.next()) |next| next else it_wrap.next().?, + ); + + // Dont swap when only the focused view is part of the layout + if (focused_node == to_swap) { + return; + } + + output.views.swap(focused_node, to_swap); + + output.arrangeViews(); + output.root.startTransaction(); +} + +fn filter(view: *View, filter_tags: u32) bool { + return !view.destroying and !view.pending.float and + !view.pending.fullscreen and view.pending.tags & filter_tags != 0; +} diff --git a/river/view_stack.zig b/river/view_stack.zig index 98ddc8e..80c3e4a 100644 --- a/river/view_stack.zig +++ b/river/view_stack.zig @@ -108,6 +108,50 @@ pub fn ViewStack(comptime T: type) type { } } + /// Swap the nodes a and b. + /// pointers to Node.T will point to the same data as before + pub fn swap(self: *Self, a: *Node, b: *Node) void { + // Set self.first and self.last + const first = self.first; + const last = self.last; + if (a == first) { + self.first = b; + } else if (a == last) { + self.last = b; + } + + if (b == first) { + self.first = a; + } else if (b == last) { + self.last = a; + } + + // This is so complicated to make sure everything works when a and b are neighbors + const a_next = if (b.next == a) b else b.next; + const a_prev = if (b.prev == a) b else b.prev; + const b_next = if (a.next == b) a else a.next; + const b_prev = if (a.prev == b) a else a.prev; + + a.next = a_next; + a.prev = a_prev; + b.next = b_next; + b.prev = b_prev; + + // Update all neighbors + if (a.next) |next| { + next.prev = a; + } + if (a.prev) |prev| { + prev.next = a; + } + if (b.next) |next| { + next.prev = b; + } + if (b.prev) |prev| { + prev.next = b; + } + } + const Direction = enum { forward, reverse,