diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 7a1aa4326..26009ef79 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -697,6 +697,13 @@ pub const Surface = extern struct { /// Whether primary paste (middle-click paste) is enabled. gtk_enable_primary_paste: bool = true, + /// How much pending horizontal scroll do we have? + pending_horizontal_scroll: f64 = 0.0, + + /// Timer to reset the amount of horizontal scroll if the user + /// stops scrolling. + pending_horizontal_scroll_reset: ?c_uint = null, + pub var offset: c_int = 0; }; @@ -1880,6 +1887,13 @@ pub const Surface = extern struct { priv.idle_rechild = null; } + if (priv.pending_horizontal_scroll_reset) |v| { + if (glib.Source.remove(v) == 0) { + log.warn("unable to remove pending horizontal scroll reset source", .{}); + } + priv.pending_horizontal_scroll_reset = null; + } + // This works around a GTK double-free bug where if you bind // to a top-level template child, it frees twice if the widget is // also the root child of the template. By unsetting the child here, @@ -2879,27 +2893,27 @@ pub const Surface = extern struct { } } - fn ecMouseScrollPrecisionBegin( + fn ecMouseScrollVerticalPrecisionBegin( _: *gtk.EventControllerScroll, self: *Self, ) callconv(.c) void { self.private().precision_scroll = true; } - fn ecMouseScrollPrecisionEnd( + fn ecMouseScrollVerticalPrecisionEnd( _: *gtk.EventControllerScroll, self: *Self, ) callconv(.c) void { self.private().precision_scroll = false; } - fn ecMouseScroll( + fn ecMouseScrollVertical( _: *gtk.EventControllerScroll, x: f64, y: f64, self: *Self, ) callconv(.c) c_int { - const priv = self.private(); + const priv: *Private = self.private(); const surface = priv.core_surface orelse return 0; // Multiply precision scrolls by 10 to get a better response from @@ -2926,6 +2940,57 @@ pub const Surface = extern struct { return 1; } + fn ecMouseScrollHorizontal( + ec: *gtk.EventControllerScroll, + x: f64, + _: f64, + self: *Self, + ) callconv(.c) c_int { + const priv: *Private = self.private(); + + switch (ec.getUnit()) { + .surface => {}, + .wheel => return @intFromBool(false), + else => return @intFromBool(false), + } + + priv.pending_horizontal_scroll += x; + + if (@abs(priv.pending_horizontal_scroll) < 120) { + if (priv.pending_horizontal_scroll_reset) |v| { + _ = glib.Source.remove(v); + priv.pending_horizontal_scroll_reset = null; + } + priv.pending_horizontal_scroll_reset = glib.timeoutAdd(500, ecMouseScrollHorizontalReset, self); + return @intFromBool(true); + } + + _ = self.as(gtk.Widget).activateAction( + if (priv.pending_horizontal_scroll < 0.0) + "tab.next-page" + else + "tab.previous-page", + null, + ); + + if (priv.pending_horizontal_scroll_reset) |v| { + _ = glib.Source.remove(v); + priv.pending_horizontal_scroll_reset = null; + } + + priv.pending_horizontal_scroll = 0.0; + + return @intFromBool(true); + } + + fn ecMouseScrollHorizontalReset(ud: ?*anyopaque) callconv(.c) c_int { + const self: *Self = @ptrCast(@alignCast(ud orelse return @intFromBool(glib.SOURCE_REMOVE))); + const priv: *Private = self.private(); + priv.pending_horizontal_scroll = 0.0; + priv.pending_horizontal_scroll_reset = null; + return @intFromBool(glib.SOURCE_REMOVE); + } + fn imPreeditStart( _: *gtk.IMMulticontext, self: *Self, @@ -3464,9 +3529,10 @@ pub const Surface = extern struct { class.bindTemplateCallback("mouse_up", &gcMouseUp); class.bindTemplateCallback("mouse_motion", &ecMouseMotion); class.bindTemplateCallback("mouse_leave", &ecMouseLeave); - class.bindTemplateCallback("scroll", &ecMouseScroll); - class.bindTemplateCallback("scroll_begin", &ecMouseScrollPrecisionBegin); - class.bindTemplateCallback("scroll_end", &ecMouseScrollPrecisionEnd); + class.bindTemplateCallback("scroll_vertical", &ecMouseScrollVertical); + class.bindTemplateCallback("scroll_vertical_begin", &ecMouseScrollVerticalPrecisionBegin); + class.bindTemplateCallback("scroll_vertical_end", &ecMouseScrollVerticalPrecisionEnd); + class.bindTemplateCallback("scroll_horizontal", &ecMouseScrollHorizontal); class.bindTemplateCallback("drop", &dtDrop); class.bindTemplateCallback("gl_realize", &glareaRealize); class.bindTemplateCallback("gl_unrealize", &glareaUnrealize); diff --git a/src/apprt/gtk/class/tab.zig b/src/apprt/gtk/class/tab.zig index ae05cd1ad..174186379 100644 --- a/src/apprt/gtk/class/tab.zig +++ b/src/apprt/gtk/class/tab.zig @@ -202,6 +202,8 @@ pub const Tab = extern struct { const actions = [_]ext.actions.Action(Self){ .init("close", actionClose, s_param_type), .init("ring-bell", actionRingBell, null), + .init("next-page", actionNextPage, null), + .init("previous-page", actionPreviousPage, null), }; _ = ext.actions.addAsGroup(Self, self, "tab", &actions); @@ -235,12 +237,17 @@ pub const Tab = extern struct { return tree.getNeedsConfirmQuit(); } - /// Get the tab page holding this tab, if any. - fn getTabPage(self: *Self) ?*adw.TabPage { - const tab_view = ext.getAncestor( + /// Get the tab view holding this tab, if any. + fn getTabView(self: *Self) ?*adw.TabView { + return ext.getAncestor( adw.TabView, self.as(gtk.Widget), - ) orelse return null; + ); + } + + /// Get the tab page holding this tab, if any. + fn getTabPage(self: *Self) ?*adw.TabPage { + const tab_view = self.getTabView() orelse return null; return tab_view.getPage(self.as(gtk.Widget)); } @@ -325,11 +332,7 @@ pub const Tab = extern struct { var str: ?[*:0]const u8 = null; param.get("&s", &str); - const tab_view = ext.getAncestor( - adw.TabView, - self.as(gtk.Widget), - ) orelse return; - + const tab_view = self.getTabView() orelse return; const page = tab_view.getPage(self.as(gtk.Widget)); const mode = std.meta.stringToEnum( @@ -372,6 +375,26 @@ pub const Tab = extern struct { page.setNeedsAttention(@intFromBool(true)); } + /// Select the next tab page. + fn actionNextPage( + _: *gio.SimpleAction, + _: ?*glib.Variant, + self: *Self, + ) callconv(.c) void { + const tab_view = self.getTabView() orelse return; + _ = tab_view.selectNextPage(); + } + + /// Select the previous tab page. + fn actionPreviousPage( + _: *gio.SimpleAction, + _: ?*glib.Variant, + self: *Self, + ) callconv(.c) void { + const tab_view = self.getTabView() orelse return; + _ = tab_view.selectPreviousPage(); + } + fn closureComputedTitle( _: *Self, config_: ?*Config, diff --git a/src/apprt/gtk/ui/1.2/surface.blp b/src/apprt/gtk/ui/1.2/surface.blp index dd6ded5de..543a3dd49 100644 --- a/src/apprt/gtk/ui/1.2/surface.blp +++ b/src/apprt/gtk/ui/1.2/surface.blp @@ -53,10 +53,15 @@ Overlay terminal_page { } EventControllerScroll { - scroll => $scroll(); - scroll-begin => $scroll_begin(); - scroll-end => $scroll_end(); - flags: both_axes; + scroll => $scroll_vertical(); + scroll-begin => $scroll_vertical_begin(); + scroll-end => $scroll_vertical_end(); + flags: vertical; + } + + EventControllerScroll { + scroll => $scroll_horizontal(); + flags: horizontal; } EventControllerMotion {