Implement "move", "snap" and "resize" commands
This commit is contained in:
		
				
					committed by
					
						 Isaac Freund
						Isaac Freund
					
				
			
			
				
	
			
			
			
						parent
						
							6f9ecd4eda
						
					
				
				
					commit
					b2f13f5bcc
				
			| @ -37,6 +37,24 @@ riverctl map normal $mod L mod-master-factor +0.05 | ||||
| riverctl map normal $mod+Shift H mod-master-count +1 | ||||
| riverctl map normal $mod+Shift L mod-master-count -1 | ||||
|  | ||||
| # Mod+Alt+{H,J,K,L} to move views | ||||
| riverctl map normal $mod+Mod1 H move left 100 | ||||
| riverctl map normal $mod+Mod1 J move down 100 | ||||
| riverctl map normal $mod+Mod1 K move up 100 | ||||
| riverctl map normal $mod+Mod1 L move right 100 | ||||
|  | ||||
| # Mod+Alt+Control+{H,J,K,L} to snap views to screen edges | ||||
| riverctl map normal $mod+Mod1+Control H snap left | ||||
| riverctl map normal $mod+Mod1+Control J snap down | ||||
| riverctl map normal $mod+Mod1+Control K snap up | ||||
| riverctl map normal $mod+Mod1+Control L snap right | ||||
|  | ||||
| # Mod+Alt+Shif+{H,J,K,L} to resize views | ||||
| riverctl map normal $mod+Mod1+Shift H resize horizontal -100 | ||||
| riverctl map normal $mod+Mod1+Shift J resize vertical 100 | ||||
| riverctl map normal $mod+Mod1+Shift K resize vertical -100 | ||||
| riverctl map normal $mod+Mod1+Shift L resize horizontal 100 | ||||
|  | ||||
| # Mod + Left Mouse Button to move views | ||||
| riverctl map-pointer normal $mod BTN_LEFT move-view | ||||
|  | ||||
|  | ||||
| @ -53,6 +53,17 @@ used to control and configure river. | ||||
| 	negative floating point number (such as 0.05) where 1 corresponds to | ||||
| 	the whole screen. | ||||
|  | ||||
| *move* *up*|*down*|*left*|*right* _delta_ | ||||
| 	Move the focused view in the specified direction by _delta_. The view will | ||||
| 	be set to floating. | ||||
|  | ||||
| *resize* *horizontal*|*vertical* _delta_ | ||||
| 	Resize the view in the given orientation by _delta_. The view will be set to | ||||
| 	floating. | ||||
|  | ||||
| *snap* *up*|*down*|*left*|*right* | ||||
| 	Snap the view to the specified screen edge. The view will be set to floating. | ||||
|  | ||||
| *send-to-output* *next*|*previous* | ||||
| 	Send the focused view to the next or the previous output. | ||||
|  | ||||
|  | ||||
| @ -24,6 +24,18 @@ pub const Direction = enum { | ||||
|     previous, | ||||
| }; | ||||
|  | ||||
| pub const PhysicalDirection = enum { | ||||
|     up, | ||||
|     down, | ||||
|     left, | ||||
|     right, | ||||
| }; | ||||
|  | ||||
| pub const Orientation = enum { | ||||
|     horizontal, | ||||
|     vertical, | ||||
| }; | ||||
|  | ||||
| // TODO: this could be replaced with a comptime hashmap | ||||
| // zig fmt: off | ||||
| const str_to_impl_fn = [_]struct { | ||||
| @ -49,11 +61,14 @@ const str_to_impl_fn = [_]struct { | ||||
|     .{ .name = "map-pointer",            .impl = @import("command/map.zig").mapPointer }, | ||||
|     .{ .name = "mod-master-count",       .impl = @import("command/mod_master_count.zig").modMasterCount }, | ||||
|     .{ .name = "mod-master-factor",      .impl = @import("command/mod_master_factor.zig").modMasterFactor }, | ||||
|     .{ .name = "move",                   .impl = @import("command/move.zig").move }, | ||||
|     .{ .name = "opacity",                .impl = @import("command/opacity.zig").opacity }, | ||||
|     .{ .name = "outer-padding",          .impl = @import("command/config.zig").outerPadding }, | ||||
|     .{ .name = "resize",                 .impl = @import("command/move.zig").resize }, | ||||
|     .{ .name = "send-to-output",         .impl = @import("command/send_to_output.zig").sendToOutput }, | ||||
|     .{ .name = "set-focused-tags",       .impl = @import("command/tags.zig").setFocusedTags }, | ||||
|     .{ .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 = "toggle-float",           .impl = @import("command/toggle_float.zig").toggleFloat }, | ||||
|     .{ .name = "toggle-focused-tags",    .impl = @import("command/tags.zig").toggleFocusedTags }, | ||||
| @ -73,6 +88,8 @@ pub const Error = error{ | ||||
|     Overflow, | ||||
|     InvalidCharacter, | ||||
|     InvalidDirection, | ||||
|     InvalidPhysicalDirection, | ||||
|     InvalidOrientation, | ||||
|     InvalidRgba, | ||||
|     InvalidValue, | ||||
|     UnknownOption, | ||||
| @ -114,6 +131,8 @@ pub fn errToMsg(err: Error) [:0]const u8 { | ||||
|         Error.Overflow => "value out of bounds", | ||||
|         Error.InvalidCharacter => "invalid character in argument", | ||||
|         Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'", | ||||
|         Error.InvalidPhysicalDirection => "invalid direction. Must be 'up', 'down', 'left' or 'right'", | ||||
|         Error.InvalidOrientation => "invalid orientation. Must be 'horizontal', or 'vertical'", | ||||
|         Error.InvalidRgba => "invalid color format, must be #RRGGBB or #RRGGBBAA", | ||||
|         Error.InvalidValue => "invalid value", | ||||
|         Error.OutOfMemory => "out of memory", | ||||
|  | ||||
							
								
								
									
										192
									
								
								river/command/move.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								river/command/move.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,192 @@ | ||||
| // This file is part of river, a dynamic tiling wayland compositor. | ||||
| // | ||||
| // Copyright 2020 Leon Henrik Plickat | ||||
| // | ||||
| // 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 <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| const std = @import("std"); | ||||
|  | ||||
| const c = @import("../c.zig"); | ||||
|  | ||||
| const Error = @import("../command.zig").Error; | ||||
| const PhysicalDirection = @import("../command.zig").PhysicalDirection; | ||||
| const Orientation = @import("../command.zig").Orientation; | ||||
| const Seat = @import("../Seat.zig"); | ||||
| const View = @import("../View.zig"); | ||||
| const Box = @import("../Box.zig"); | ||||
|  | ||||
| pub fn move( | ||||
|     allocator: *std.mem.Allocator, | ||||
|     seat: *Seat, | ||||
|     args: []const []const u8, | ||||
|     out: *?[]const u8, | ||||
| ) Error!void { | ||||
|     if (args.len < 3) return Error.NotEnoughArguments; | ||||
|     if (args.len > 3) return Error.TooManyArguments; | ||||
|  | ||||
|     const delta = try std.fmt.parseInt(i32, args[2], 10); | ||||
|     const direction = std.meta.stringToEnum(PhysicalDirection, args[1]) orelse | ||||
|         return Error.InvalidPhysicalDirection; | ||||
|  | ||||
|     const view = getView(seat) orelse return; | ||||
|     switch (direction) { | ||||
|         .up => moveVertical(view, -1 * delta), | ||||
|         .down => moveVertical(view, delta), | ||||
|         .left => moveHorizontal(view, -1 * delta), | ||||
|         .right => moveHorizontal(view, delta), | ||||
|     } | ||||
|  | ||||
|     apply(view); | ||||
| } | ||||
|  | ||||
| pub fn snap( | ||||
|     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; | ||||
|  | ||||
|     const direction = std.meta.stringToEnum(PhysicalDirection, args[1]) orelse | ||||
|         return Error.InvalidPhysicalDirection; | ||||
|  | ||||
|     const view = get_view(seat) orelse return; | ||||
|     const output_box = get_output_dimensions(view); | ||||
|     const view = getView(seat) orelse return; | ||||
|     const border_width = @intCast(i32, view.output.root.server.config.border_width); | ||||
|     switch (direction) { | ||||
|         .up => view.pending.box.y = border_width, | ||||
|         .down => view.pending.box.y = | ||||
|             @intCast(i32, output_box.height - view.pending.box.height) - border_width, | ||||
|         .left => view.pending.box.x = border_width, | ||||
|         .right => view.pending.box.x = | ||||
|             @intCast(i32, output_box.width - view.pending.box.width) - border_width, | ||||
|     } | ||||
|  | ||||
|     apply(view); | ||||
| } | ||||
|  | ||||
| pub fn resize( | ||||
|     allocator: *std.mem.Allocator, | ||||
|     seat: *Seat, | ||||
|     args: []const []const u8, | ||||
|     out: *?[]const u8, | ||||
| ) Error!void { | ||||
|     if (args.len < 3) return Error.NotEnoughArguments; | ||||
|     if (args.len > 3) return Error.TooManyArguments; | ||||
|  | ||||
|     const delta = try std.fmt.parseInt(i32, args[2], 10); | ||||
|     const orientation = std.meta.stringToEnum(Orientation, args[1]) orelse | ||||
|         return Error.InvalidOrientation; | ||||
|  | ||||
|     const view = get_view(seat) orelse return; | ||||
|     const output_box = get_output_dimensions(view); | ||||
|     const view = getView(seat) orelse return; | ||||
|     const border_width = @intCast(i32, view.output.root.server.config.border_width); | ||||
|     switch (orientation) { | ||||
|         .horizontal => { | ||||
|             var real_delta: i32 = @intCast(i32, view.pending.box.width); | ||||
|             if (delta > 0) { | ||||
|                 view.pending.box.width += @intCast(u32, delta); | ||||
|             } else { | ||||
|                 // Prevent underflow | ||||
|                 view.pending.box.width -= | ||||
|                     std.math.min(view.pending.box.width, @intCast(u32, -1 * delta)); | ||||
|             } | ||||
|             view.applyConstraints(); | ||||
|             // Do not grow bigger than the output | ||||
|             view.pending.box.width = std.math.min( | ||||
|                 view.pending.box.width, | ||||
|                 output_box.width - @intCast(u32, 2 * border_width), | ||||
|             ); | ||||
|             real_delta -= @intCast(i32, view.pending.box.width); | ||||
|             moveHorizontal(view, @divFloor(real_delta, 2)); | ||||
|         }, | ||||
|         .vertical => { | ||||
|             var real_delta: i32 = @intCast(i32, view.pending.box.height); | ||||
|             if (delta > 0) { | ||||
|                 view.pending.box.height += @intCast(u32, delta); | ||||
|             } else { | ||||
|                 // Prevent underflow | ||||
|                 view.pending.box.height -= | ||||
|                     std.math.min(view.pending.box.height, @intCast(u32, -1 * delta)); | ||||
|             } | ||||
|             view.applyConstraints(); | ||||
|             // Do not grow bigger than the output | ||||
|             view.pending.box.height = std.math.min( | ||||
|                 view.pending.box.height, | ||||
|                 output_box.height - @intCast(u32, 2 * border_width), | ||||
|             ); | ||||
|             real_delta -= @intCast(i32, view.pending.box.height); | ||||
|             moveVertical(view, @divFloor(real_delta, 2)); | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     apply(view); | ||||
| } | ||||
|  | ||||
| fn apply(view: *View) void { | ||||
|     // Set the view to floating but keep the position and dimensions | ||||
|     view.pending.float = true; | ||||
|     view.float_box = view.pending.box; | ||||
|  | ||||
|     view.applyPending(); | ||||
| } | ||||
|  | ||||
| fn getView(seat: *Seat) ?*View { | ||||
|     if (seat.focused != .view) return null; | ||||
|     const view = seat.focused.view; | ||||
|  | ||||
|     // Do not touch fullscreen views | ||||
|     if (view.pending.fullscreen) return null; | ||||
|  | ||||
|     // Do not touch views which are the target of a cursor action | ||||
|     if (seat.input_manager.isCursorActionTarget(view)) return null; | ||||
|  | ||||
|     return view; | ||||
| } | ||||
|  | ||||
| fn get_output_dimensions(view: *View) Box { | ||||
|     var output_width: c_int = undefined; | ||||
|     var output_height: c_int = undefined; | ||||
|     c.wlr_output_effective_resolution(view.output.wlr_output, &output_width, &output_height); | ||||
|     const box: Box = .{ | ||||
|         .x = 0, | ||||
|         .y = 0, | ||||
|         .width = @intCast(u32, output_width), | ||||
|         .height = @intCast(u32, output_height), | ||||
|     }; | ||||
|     return box; | ||||
| } | ||||
|  | ||||
| fn moveVertical(view: *View, delta: i32) void { | ||||
|     const output_box = view.output.get_output_dimensions(view); | ||||
|     const border_width = @intCast(i32, view.output.root.server.config.border_width); | ||||
|     view.pending.box.y = std.math.clamp( | ||||
|         view.pending.box.y + delta, | ||||
|         border_width, | ||||
|         @intCast(i32, output_box.height - view.pending.box.height) - border_width, | ||||
|     ); | ||||
| } | ||||
|  | ||||
| fn moveHorizontal(view: *View, delta: i32) void { | ||||
|     const output_box = view.output.get_output_dimensions(view); | ||||
|     const border_width = @intCast(i32, view.output.root.server.config.border_width); | ||||
|     view.pending.box.x = std.math.clamp( | ||||
|         view.pending.box.x + delta, | ||||
|         border_width, | ||||
|         @intCast(i32, output_box.width - view.pending.box.width) - border_width, | ||||
|     ); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user