From fe82c7d367d60c7f2b5b40c1f3cde916a1daf2a2 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Fri, 1 Nov 2024 15:13:37 +0300 Subject: [PATCH] First draft of feature implementation --- macos/Sources/App/macOS/AppDelegate.swift | 4 +-- src/Surface.zig | 27 ++++++++++------- src/apprt/action.zig | 20 ++++++++----- src/apprt/gtk/App.zig | 12 ++++---- src/apprt/gtk/Split.zig | 33 ++++++++++++++++----- src/apprt/gtk/Window.zig | 8 +++--- src/config/Config.zig | 8 +++--- src/input/Binding.zig | 35 ++++++++++++++++++++--- 8 files changed, 103 insertions(+), 44 deletions(-) diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 8179e1950..925af9e0e 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -312,8 +312,8 @@ class AppDelegate: NSObject, syncMenuShortcut(action: "close_surface", menuItem: self.menuClose) syncMenuShortcut(action: "close_window", menuItem: self.menuCloseWindow) syncMenuShortcut(action: "close_all_windows", menuItem: self.menuCloseAllWindows) - syncMenuShortcut(action: "new_split:right", menuItem: self.menuSplitRight) - syncMenuShortcut(action: "new_split:down", menuItem: self.menuSplitDown) + syncMenuShortcut(action: "new_split:right,50%", menuItem: self.menuSplitRight) + syncMenuShortcut(action: "new_split:down,50%", menuItem: self.menuSplitDown) syncMenuShortcut(action: "copy_to_clipboard", menuItem: self.menuCopy) syncMenuShortcut(action: "paste_from_clipboard", menuItem: self.menuPaste) diff --git a/src/Surface.zig b/src/Surface.zig index 82d1240eb..b00672896 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3919,18 +3919,25 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .{ .amount = position }, ), - .new_split => |direction| try self.rt_app.performAction( + .new_split => |value| try self.rt_app.performAction( .{ .surface = self }, .new_split, - switch (direction) { - .right => .right, - .left => .left, - .down => .down, - .up => .up, - .auto => if (self.screen_size.width > self.screen_size.height) - .right - else - .down, + .{ + .percent = std.fmt.parseInt( + u16, + value[1], + 10, + ) catch return error.InvalidType, + .direction = switch (value[0]) { + .right => .right, + .left => .left, + .down => .down, + .up => .up, + .auto => if (self.screen_size.width > self.screen_size.height) + .right + else + .down, + }, }, ), diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 2c37ca270..925445fd9 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -282,13 +282,19 @@ pub const Action = union(Key) { } }; -// This is made extern (c_int) to make interop easier with our embedded -// runtime. The small size cost doesn't make a difference in our union. -pub const SplitDirection = enum(c_int) { - right, - down, - left, - up, +/// The percentage and direction to create the split +pub const SplitDirection = extern struct { + percent: u16, + direction: Direction, + + // This is made extern (c_int) to make interop easier with our embedded + // runtime. The small size cost doesn't make a difference in our union. + pub const Direction = enum(c_int) { + right, + down, + left, + up, + }; }; // This is made extern (c_int) to make interop easier with our embedded diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index af664d720..462e452a1 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -548,13 +548,13 @@ fn moveTab(_: *App, target: apprt.Target, move_tab: apprt.action.MoveTab) void { fn newSplit( self: *App, target: apprt.Target, - direction: apprt.action.SplitDirection, + new_split: apprt.action.SplitDirection, ) !void { switch (target) { .app => {}, .surface => |v| { const alloc = self.core_app.alloc; - _ = try Split.create(alloc, v.rt_surface, direction); + _ = try Split.create(alloc, v.rt_surface, new_split); }, } } @@ -863,10 +863,10 @@ fn syncActionAccelerators(self: *App) !void { try self.syncActionAccelerator("win.close", .{ .close_surface = {} }); try self.syncActionAccelerator("win.new_window", .{ .new_window = {} }); try self.syncActionAccelerator("win.new_tab", .{ .new_tab = {} }); - try self.syncActionAccelerator("win.split_right", .{ .new_split = .right }); - try self.syncActionAccelerator("win.split_down", .{ .new_split = .down }); - try self.syncActionAccelerator("win.split_left", .{ .new_split = .left }); - try self.syncActionAccelerator("win.split_up", .{ .new_split = .up }); + try self.syncActionAccelerator("win.split_right", .{ .new_split = .{ .right, "50%" } }); + try self.syncActionAccelerator("win.split_down", .{ .new_split = .{ .down, "50%" } }); + try self.syncActionAccelerator("win.split_left", .{ .new_split = .{ .left, "50%" } }); + try self.syncActionAccelerator("win.split_up", .{ .new_split = .{ .up, "50%" } }); try self.syncActionAccelerator("win.copy", .{ .copy_to_clipboard = {} }); try self.syncActionAccelerator("win.paste", .{ .paste_from_clipboard = {} }); try self.syncActionAccelerator("win.reset", .{ .reset = {} }); diff --git a/src/apprt/gtk/Split.zig b/src/apprt/gtk/Split.zig index 5afac6f5b..305edb8d9 100644 --- a/src/apprt/gtk/Split.zig +++ b/src/apprt/gtk/Split.zig @@ -20,7 +20,7 @@ pub const Orientation = enum { horizontal, vertical, - pub fn fromDirection(direction: apprt.action.SplitDirection) Orientation { + pub fn fromDirection(direction: apprt.action.SplitDirection.Direction) Orientation { return switch (direction) { .right, .left => .horizontal, .down, .up => .vertical, @@ -57,18 +57,18 @@ bottom_right: Surface.Container.Elem, pub fn create( alloc: Allocator, sibling: *Surface, - direction: apprt.action.SplitDirection, + new_split: apprt.action.SplitDirection, ) !*Split { var split = try alloc.create(Split); errdefer alloc.destroy(split); - try split.init(sibling, direction); + try split.init(sibling, new_split); return split; } pub fn init( self: *Split, sibling: *Surface, - direction: apprt.action.SplitDirection, + new_split: apprt.action.SplitDirection, ) !void { // Create the new child surface for the other direction. const alloc = sibling.app.core_app.alloc; @@ -79,7 +79,7 @@ pub fn init( sibling.dimSurface(); // Create the actual GTKPaned, attach the proper children. - const orientation: c_uint = switch (direction) { + const orientation: c_uint = switch (new_split.direction) { .right, .left => c.GTK_ORIENTATION_HORIZONTAL, .down, .up => c.GTK_ORIENTATION_VERTICAL, }; @@ -94,7 +94,7 @@ pub fn init( // we're inheriting its parent. The sibling points to its location // in the split, and the surface points to the other location. const container = sibling.container; - const tl: *Surface, const br: *Surface = switch (direction) { + const tl: *Surface, const br: *Surface = switch (new_split.direction) { .right, .down => right_down: { sibling.container = .{ .split_tl = &self.top_left }; surface.container = .{ .split_br = &self.bottom_right }; @@ -113,7 +113,7 @@ pub fn init( .container = container, .top_left = .{ .surface = tl }, .bottom_right = .{ .surface = br }, - .orientation = Orientation.fromDirection(direction), + .orientation = Orientation.fromDirection(new_split.direction), }; // Replace the previous containers element with our split. @@ -125,6 +125,25 @@ pub fn init( // added to the paned. self.updateChildren(); + // Skip resize logic if percentage is 50 (this is the default behavior) + if (new_split.percent != 50) { + const allocation = sibling.size; + const split_percentage: f32 = @as(f32, @floatFromInt(new_split.percent)) / 100; + const total_surface_size: f32 = switch (self.orientation) { + .horizontal => @floatFromInt(allocation.width), + .vertical => @floatFromInt(allocation.height), + }; + + // percentage to apply based on direction + const pct = switch (new_split.direction) { + .right, .down => 1 - split_percentage, + .left, .up => split_percentage, + }; + + const divider_position = @as(c_int, @intFromFloat(total_surface_size * pct)); + c.gtk_paned_set_position(self.paned, divider_position); + } + // The new surface should always grab focus surface.grabFocus(); } diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 7f3c50789..a062c415e 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -811,7 +811,7 @@ fn gtkActionSplitRight( ) callconv(.C) void { const self: *Window = @ptrCast(@alignCast(ud orelse return)); const surface = self.actionSurface() orelse return; - _ = surface.performBindingAction(.{ .new_split = .right }) catch |err| { + _ = surface.performBindingAction(.{ .new_split = .{ .right, "50%" } }) catch |err| { log.warn("error performing binding action error={}", .{err}); return; }; @@ -824,7 +824,7 @@ fn gtkActionSplitDown( ) callconv(.C) void { const self: *Window = @ptrCast(@alignCast(ud orelse return)); const surface = self.actionSurface() orelse return; - _ = surface.performBindingAction(.{ .new_split = .down }) catch |err| { + _ = surface.performBindingAction(.{ .new_split = .{ .down, "50%" } }) catch |err| { log.warn("error performing binding action error={}", .{err}); return; }; @@ -837,7 +837,7 @@ fn gtkActionSplitLeft( ) callconv(.C) void { const self: *Window = @ptrCast(@alignCast(ud orelse return)); const surface = self.actionSurface() orelse return; - _ = surface.performBindingAction(.{ .new_split = .left }) catch |err| { + _ = surface.performBindingAction(.{ .new_split = .{ .left, "50%" } }) catch |err| { log.warn("error performing binding action error={}", .{err}); return; }; @@ -850,7 +850,7 @@ fn gtkActionSplitUp( ) callconv(.C) void { const self: *Window = @ptrCast(@alignCast(ud orelse return)); const surface = self.actionSurface() orelse return; - _ = surface.performBindingAction(.{ .new_split = .up }) catch |err| { + _ = surface.performBindingAction(.{ .new_split = .{ .up, "50%" } }) catch |err| { log.warn("error performing binding action error={}", .{err}); return; }; diff --git a/src/config/Config.zig b/src/config/Config.zig index a674046e1..3afbc9fd1 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1908,12 +1908,12 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { try result.keybind.set.put( alloc, .{ .key = .{ .translated = .o }, .mods = .{ .ctrl = true, .shift = true } }, - .{ .new_split = .right }, + .{ .new_split = .{ .right, "50%" } }, ); try result.keybind.set.put( alloc, .{ .key = .{ .translated = .e }, .mods = .{ .ctrl = true, .shift = true } }, - .{ .new_split = .down }, + .{ .new_split = .{ .down, "50%" } }, ); try result.keybind.set.put( alloc, @@ -2171,12 +2171,12 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { try result.keybind.set.put( alloc, .{ .key = .{ .translated = .d }, .mods = .{ .super = true } }, - .{ .new_split = .right }, + .{ .new_split = .{ .right, "50%" } }, ); try result.keybind.set.put( alloc, .{ .key = .{ .translated = .d }, .mods = .{ .super = true, .shift = true } }, - .{ .new_split = .down }, + .{ .new_split = .{ .down, "50%" } }, ); try result.keybind.set.put( alloc, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 5a4cd3f3e..92080a113 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -310,9 +310,9 @@ pub const Action = union(enum) { /// This only works with libadwaita enabled currently. toggle_tab_overview: void, - /// Create a new split in the given direction. The new split will appear in + /// Create a new split in the given direction and percentage. The new split will appear in /// the direction given. - new_split: SplitDirection, + new_split: SplitParameter, /// Focus on a split in a given direction. goto_split: SplitFocusDirection, @@ -454,6 +454,13 @@ pub const Action = union(enum) { auto, // splits along the larger direction }; + pub const Percentage = []const u8; + + pub const SplitParameter = struct { + SplitDirection, + Percentage, + }; + pub const SplitFocusDirection = enum { previous, next, @@ -500,6 +507,14 @@ pub const Action = union(enum) { return std.fmt.parseFloat(T, value) catch return Error.InvalidFormat; } + fn parsePercentage(value: []const u8) !Percentage { + if (value.len < 2) return Error.InvalidFormat; + if (value[value.len - 1] != '%') return Error.InvalidFormat; + const percent = value[0 .. value.len - 1]; + _ = std.fmt.parseInt(u16, percent, 10) catch return Error.InvalidFormat; + return percent; + } + fn parseParameter( comptime field: std.builtin.Type.UnionField, param: []const u8, @@ -521,6 +536,7 @@ pub const Action = union(enum) { .Enum => try parseEnum(field_.type, next), .Int => try parseInt(field_.type, next), .Float => try parseFloat(field_.type, next), + .Pointer => if (field_.type == Percentage) try parsePercentage(next), else => unreachable, }; } @@ -1688,10 +1704,21 @@ test "parse: action with enum" { // parameter { - const binding = try parseSingle("a=new_split:right"); + // Note: The "50%" in the binding string gets parsed to just "50" + const binding = try parseSingle("a=new_split:right,50%"); try testing.expect(binding.action == .new_split); - try testing.expectEqual(Action.SplitDirection.right, binding.action.new_split); + try testing.expectEqual(Action.SplitDirection.right, binding.action.new_split[0]); + try testing.expectEqualSlices(u8, "50", binding.action.new_split[1]); } + + // missing unit % + try testing.expectError(Error.InvalidFormat, parseSingle("a=new_split:right,50")); + + // too many + try testing.expectError(Error.InvalidFormat, parseSingle("a=new_split:right,30%,50%")); + + // invalid type + try testing.expectError(Error.InvalidFormat, parseSingle("a=new_split:right,0.5%")); } test "parse: action with int" {