mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
gtk: get 1st version of GTK splits working
This commit is contained in:

committed by
Mitchell Hashimoto

parent
aba1b85503
commit
d88898fc61
@ -13,23 +13,8 @@ const Position = @import("parent.zig").Position;
|
|||||||
const Parent = @import("parent.zig").Parent;
|
const Parent = @import("parent.zig").Parent;
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
|
|
||||||
const Child = union(enum) {
|
|
||||||
surface: *Surface,
|
|
||||||
paned: *Paned,
|
|
||||||
empty: void,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn is_empty(self: Self) bool {
|
|
||||||
switch (self) {
|
|
||||||
Child.empty => return true,
|
|
||||||
else => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// We'll need to keep a reference to the Window this belongs to for various reasons
|
/// We'll need to keep a reference to the Window this belongs to for various reasons
|
||||||
Window: *c.GtkWindow,
|
window: *Window,
|
||||||
|
|
||||||
// We keep track of the tab label's text so that if a child widget of this pane
|
// We keep track of the tab label's text so that if a child widget of this pane
|
||||||
// gets focus (and is a Surface) we can reset the tab label appropriately
|
// gets focus (and is a Surface) we can reset the tab label appropriately
|
||||||
@ -40,8 +25,8 @@ paned: *c.GtkPaned,
|
|||||||
|
|
||||||
// We have two children, each of which can be either a Surface, another pane,
|
// We have two children, each of which can be either a Surface, another pane,
|
||||||
// or empty. We're going to keep track of which each child is here.
|
// or empty. We're going to keep track of which each child is here.
|
||||||
child1: Child,
|
child1: Tab.Child,
|
||||||
child2: Child,
|
child2: Tab.Child,
|
||||||
|
|
||||||
// We also hold a reference to our parent widget, so that when we close we can either
|
// We also hold a reference to our parent widget, so that when we close we can either
|
||||||
// maximize the parent pane, or close the tab.
|
// maximize the parent pane, or close the tab.
|
||||||
@ -59,8 +44,8 @@ pub fn init(self: *Paned, window: *Window, label_text: *c.GtkWidget) !void {
|
|||||||
.window = window,
|
.window = window,
|
||||||
.label_text = label_text,
|
.label_text = label_text,
|
||||||
.paned = undefined,
|
.paned = undefined,
|
||||||
.child1 = Child{.empty},
|
.child1 = .empty,
|
||||||
.child2 = Child{.empty},
|
.child2 = .empty,
|
||||||
.parent = undefined,
|
.parent = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,16 +54,16 @@ pub fn init(self: *Paned, window: *Window, label_text: *c.GtkWidget) !void {
|
|||||||
errdefer c.gtk_widget_destroy(paned);
|
errdefer c.gtk_widget_destroy(paned);
|
||||||
self.paned = gtk_paned;
|
self.paned = gtk_paned;
|
||||||
|
|
||||||
const surface = try self.newSurface(self.window.actionSurface());
|
// const surface = try self.newSurface(self.window.actionSurface());
|
||||||
// We know that both panels are currently empty, so we maximize the 1st
|
// // We know that both panels are currently empty, so we maximize the 1st
|
||||||
c.gtk_paned_set_position(self.paned, 100);
|
// c.gtk_paned_set_position(self.paned, 100);
|
||||||
const child_widget: *c.GtkWidget = @ptrCast(surface.gl_area);
|
// const child_widget: *c.GtkWidget = @ptrCast(surface.gl_area);
|
||||||
const child = Child{ .surface = surface };
|
// const child = Child{ .surface = surface };
|
||||||
c.gtk_paned_pack1(self.paned, child_widget, 1, 1);
|
// c.gtk_paned_pack1(self.paned, child_widget, 1, 1);
|
||||||
self.child1 = child;
|
// self.child1 = child;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newSurface(self: *Paned, parent_: ?*CoreSurface) !*Surface {
|
pub fn newSurface(self: *Paned, tab: *Tab, parent_: ?*CoreSurface) !*Surface {
|
||||||
// Grab a surface allocation we'll need it later.
|
// Grab a surface allocation we'll need it later.
|
||||||
var surface = try self.window.app.core_app.alloc.create(Surface);
|
var surface = try self.window.app.core_app.alloc.create(Surface);
|
||||||
errdefer self.window.app.core_app.alloc.destroy(surface);
|
errdefer self.window.app.core_app.alloc.destroy(surface);
|
||||||
@ -95,8 +80,15 @@ pub fn newSurface(self: *Paned, parent_: ?*CoreSurface) !*Surface {
|
|||||||
// wait for the "realize" callback from GTK to know that the OpenGL
|
// wait for the "realize" callback from GTK to know that the OpenGL
|
||||||
// context is ready. See Surface docs for more info.
|
// context is ready. See Surface docs for more info.
|
||||||
const gl_area = c.gtk_gl_area_new();
|
const gl_area = c.gtk_gl_area_new();
|
||||||
|
c.gtk_widget_set_hexpand(gl_area, 1);
|
||||||
|
c.gtk_widget_set_vexpand(gl_area, 1);
|
||||||
try surface.init(self.window.app, .{
|
try surface.init(self.window.app, .{
|
||||||
.window = self,
|
.window = self.window,
|
||||||
|
.tab = tab,
|
||||||
|
.parent = .{ .paned = .{
|
||||||
|
self,
|
||||||
|
Position.end,
|
||||||
|
} },
|
||||||
.gl_area = @ptrCast(gl_area),
|
.gl_area = @ptrCast(gl_area),
|
||||||
.title_label = @ptrCast(self.label_text),
|
.title_label = @ptrCast(self.label_text),
|
||||||
.font_size = font_size,
|
.font_size = font_size,
|
||||||
@ -104,62 +96,35 @@ pub fn newSurface(self: *Paned, parent_: ?*CoreSurface) !*Surface {
|
|||||||
return surface;
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addChild1Surface(self: *Paned, surface: *Surface) void {
|
pub fn removeChildren(self: *Paned) void {
|
||||||
assert(self.child1.is_empty());
|
c.gtk_paned_set_start_child(@ptrCast(self.paned), null);
|
||||||
self.child1 = Child{ .surface = surface };
|
c.gtk_paned_set_end_child(@ptrCast(self.paned), null);
|
||||||
surface.parent = Surface.Parent{ .paned = .{ self, Position.start } };
|
|
||||||
c.gtk_paned_pack1(@ptrCast(self.paned), @ptrCast(surface.gl_area), 1, 0);
|
|
||||||
if (self.child2.is_empty()) {
|
|
||||||
c.gtk_paned_set_position(self.paned, 100);
|
|
||||||
} else {
|
|
||||||
c.gtk_paned_set_position(self.paned, 50);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addChild2Surface(self: *Paned, surface: *Surface) void {
|
pub fn addChild1Surface(self: *Paned, surface: *Surface) void {
|
||||||
assert(self.child1.is_empty());
|
assert(self.child1.is_empty());
|
||||||
self.child2 = Child{ .surface = surface };
|
self.child1 = Tab.Child{ .surface = surface };
|
||||||
surface.parent = Surface.Parent{ .paned = .{ self, Position.end } };
|
surface.setParent(Parent{ .paned = .{ self, Position.start } });
|
||||||
c.gtk_paned_pack2(@ptrCast(self.paned), @ptrCast(surface.gl_area), 1, 0);
|
c.gtk_paned_set_start_child(@ptrCast(self.paned), @ptrCast(surface.gl_area));
|
||||||
if (self.child2.is_empty()) {
|
|
||||||
c.gtk_paned_set_position(self.paned, 0);
|
|
||||||
} else {
|
|
||||||
c.gtk_paned_set_position(self.paned, 50);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addChild1Paned(self: *Paned, child: *Paned) void {
|
pub fn addChild2Surface(self: *Paned, surface: *Surface) void {
|
||||||
assert(self.child1.is_empty());
|
assert(self.child2.is_empty());
|
||||||
self.child1 = Child{ .paned = child };
|
self.child2 = Tab.Child{ .surface = surface };
|
||||||
child.parent = Parent{ .paned = .{ self, Position.start } };
|
surface.setParent(Parent{ .paned = .{ self, Position.end } });
|
||||||
c.gtk_paned_pack1(@ptrCast(self.paned), @ptrCast(child.paned), 1, 0);
|
c.gtk_paned_set_end_child(@ptrCast(self.paned), @ptrCast(surface.gl_area));
|
||||||
if (self.child2.is_empty()) {
|
|
||||||
c.gtk_paned_set_position(self.paned, 100);
|
|
||||||
} else {
|
|
||||||
c.gtk_paned_set_position(self.paned, 50);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addChild2Paned(self: *Paned, child: *Paned) void {
|
pub fn removeChild1(self: *Paned) void {
|
||||||
assert(self.child1.is_empty());
|
|
||||||
self.child2 = Child{ .paned = child };
|
|
||||||
child.parent = Parent{ .paned = .{ self, Position.end } };
|
|
||||||
c.gtk_paned_pack2(@ptrCast(self.paned), @ptrCast(child.paned), 1, 0);
|
|
||||||
if (self.child2.is_empty()) {
|
|
||||||
c.gtk_paned_set_position(self.paned, 0);
|
|
||||||
} else {
|
|
||||||
c.gtk_paned_set_position(self.paned, 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn removeChild1(self: *Paned) void {
|
|
||||||
assert(!self.child1.is_empty());
|
assert(!self.child1.is_empty());
|
||||||
// todo
|
self.child1 = .empty;
|
||||||
|
c.gtk_paned_set_start_child(@ptrCast(self.paned), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn removeChild2(self: *Paned) void {
|
pub fn removeChild2(self: *Paned) void {
|
||||||
assert(!self.child1.is_empty());
|
assert(!self.child2.is_empty());
|
||||||
// todo
|
self.child2 = .empty;
|
||||||
|
c.gtk_paned_set_end_child(@ptrCast(self.paned), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn splitStartPosition(self: *Paned, orientation: c.GtkOrientation) !void {
|
pub fn splitStartPosition(self: *Paned, orientation: c.GtkOrientation) !void {
|
||||||
|
@ -32,6 +32,12 @@ pub const Options = struct {
|
|||||||
/// The window that this surface is attached to.
|
/// The window that this surface is attached to.
|
||||||
window: *Window,
|
window: *Window,
|
||||||
|
|
||||||
|
/// The tab that this surface is attached to.
|
||||||
|
tab: *Tab,
|
||||||
|
|
||||||
|
/// The parent this surface is created under.
|
||||||
|
parent: Parent,
|
||||||
|
|
||||||
/// The GL area that this surface should draw to.
|
/// The GL area that this surface should draw to.
|
||||||
gl_area: *c.GtkGLArea,
|
gl_area: *c.GtkGLArea,
|
||||||
|
|
||||||
@ -41,13 +47,6 @@ pub const Options = struct {
|
|||||||
|
|
||||||
/// A font size to set on the surface once it is initialized.
|
/// A font size to set on the surface once it is initialized.
|
||||||
font_size: ?font.face.DesiredSize = null,
|
font_size: ?font.face.DesiredSize = null,
|
||||||
|
|
||||||
/// True if this surface has a parent. This is a bit of a hack currently
|
|
||||||
/// to work around newConfig unconditinally inheriting the working
|
|
||||||
/// directory. The proper long term fix is to have the working directory
|
|
||||||
/// inherited upstream likely at the point where this field would be set,
|
|
||||||
/// then remove this field.
|
|
||||||
parent: bool = false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Where the title of this surface will go.
|
/// Where the title of this surface will go.
|
||||||
@ -61,16 +60,16 @@ const Title = union(enum) {
|
|||||||
/// surface has been initialized.
|
/// surface has been initialized.
|
||||||
realized: bool = false,
|
realized: bool = false,
|
||||||
|
|
||||||
/// See Options.parent
|
|
||||||
parent: bool = false,
|
|
||||||
|
|
||||||
/// The app we're part of
|
/// The app we're part of
|
||||||
app: *App,
|
app: *App,
|
||||||
|
|
||||||
/// The window we're part of
|
/// The window we're part of
|
||||||
window: *Window,
|
window: *Window,
|
||||||
|
|
||||||
/// Our parent widget
|
/// The tab we're part of
|
||||||
|
tab: *Tab,
|
||||||
|
|
||||||
|
/// The parent we belong to
|
||||||
parent: Parent,
|
parent: Parent,
|
||||||
|
|
||||||
/// Our GTK area
|
/// Our GTK area
|
||||||
@ -161,14 +160,14 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
|||||||
self.* = .{
|
self.* = .{
|
||||||
.app = app,
|
.app = app,
|
||||||
.window = opts.window,
|
.window = opts.window,
|
||||||
.parent = Parent.none,
|
.tab = opts.tab,
|
||||||
|
.parent = opts.parent,
|
||||||
.gl_area = opts.gl_area,
|
.gl_area = opts.gl_area,
|
||||||
.title = if (opts.title_label) |label| .{
|
.title = if (opts.title_label) |label| .{
|
||||||
.label = label,
|
.label = label,
|
||||||
} else .{ .none = {} },
|
} else .{ .none = {} },
|
||||||
.core_surface = undefined,
|
.core_surface = undefined,
|
||||||
.font_size = opts.font_size,
|
.font_size = opts.font_size,
|
||||||
.parent = opts.parent,
|
|
||||||
.size = .{ .width = 800, .height = 600 },
|
.size = .{ .width = 800, .height = 600 },
|
||||||
.cursor_pos = .{ .x = 0, .y = 0 },
|
.cursor_pos = .{ .x = 0, .y = 0 },
|
||||||
.im_context = im_context,
|
.im_context = im_context,
|
||||||
@ -347,6 +346,51 @@ pub fn toggleFullscreen(self: *Surface, mac_non_native: configpkg.NonNativeFulls
|
|||||||
self.window.toggleFullscreen(mac_non_native);
|
self.window.toggleFullscreen(mac_non_native);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn newSplit(self: *Surface, direction: input.SplitDirection) !void {
|
||||||
|
log.info("surface.newSplit. direction={}", .{direction});
|
||||||
|
|
||||||
|
switch (self.parent) {
|
||||||
|
.none => {
|
||||||
|
log.info("no parent\n", .{});
|
||||||
|
},
|
||||||
|
.paned => {
|
||||||
|
log.info("parent is paned \n", .{});
|
||||||
|
},
|
||||||
|
.tab => |tab| {
|
||||||
|
const tab_idx = for (self.window.tabs.items, 0..) |t, i| {
|
||||||
|
if (t == tab) break i;
|
||||||
|
} else null;
|
||||||
|
|
||||||
|
const label_text: ?*c.GtkWidget = switch (self.title) {
|
||||||
|
.none => null,
|
||||||
|
.label => |label| l: {
|
||||||
|
const widget = @as(*c.GtkWidget, @ptrCast(@alignCast(label)));
|
||||||
|
break :l widget;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (label_text) |text| {
|
||||||
|
tab.removeChild();
|
||||||
|
|
||||||
|
const paned = try Paned.create(self.app.core_app.alloc, self.window, text);
|
||||||
|
|
||||||
|
const new_surface = try paned.newSurface(tab, &self.core_surface);
|
||||||
|
// // This sets .parent on each surface
|
||||||
|
paned.addChild1Surface(self);
|
||||||
|
paned.addChild2Surface(new_surface);
|
||||||
|
|
||||||
|
tab.setChild(.{ .paned = paned });
|
||||||
|
|
||||||
|
// FOCUS ON NEW SURFACE
|
||||||
|
const widget = @as(*c.GtkWidget, @ptrCast(new_surface.gl_area));
|
||||||
|
_ = c.gtk_widget_grab_focus(widget);
|
||||||
|
} else {
|
||||||
|
log.info("no label text: {?}\n", .{tab_idx});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn newTab(self: *Surface) !void {
|
pub fn newTab(self: *Surface) !void {
|
||||||
try self.window.newTab(&self.core_surface);
|
try self.window.newTab(&self.core_surface);
|
||||||
}
|
}
|
||||||
@ -429,6 +473,10 @@ pub fn setTitle(self: *Surface, slice: [:0]const u8) !void {
|
|||||||
// ));
|
// ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setParent(self: *Surface, parent: Parent) void {
|
||||||
|
self.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setMouseShape(
|
pub fn setMouseShape(
|
||||||
self: *Surface,
|
self: *Surface,
|
||||||
shape: terminal.MouseShape,
|
shape: terminal.MouseShape,
|
||||||
@ -755,9 +803,20 @@ fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
|||||||
log.debug("gl destroy", .{});
|
log.debug("gl destroy", .{});
|
||||||
|
|
||||||
const self = userdataSelf(ud.?);
|
const self = userdataSelf(ud.?);
|
||||||
|
switch (self.parent) {
|
||||||
|
.none, .tab => {
|
||||||
const alloc = self.app.core_app.alloc;
|
const alloc = self.app.core_app.alloc;
|
||||||
self.deinit();
|
self.deinit();
|
||||||
alloc.destroy(self);
|
alloc.destroy(self);
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
|
||||||
|
// const alloc = self.app.core_app.alloc;
|
||||||
|
// self.deinit();
|
||||||
|
// alloc.destroy(self);
|
||||||
|
log.debug("TODO: no destroy", .{});
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scale x/y by the GDK device scale.
|
/// Scale x/y by the GDK device scale.
|
||||||
@ -1226,7 +1285,6 @@ fn gtkInputCommit(
|
|||||||
|
|
||||||
fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) void {
|
fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) void {
|
||||||
const self = userdataSelf(ud.?);
|
const self = userdataSelf(ud.?);
|
||||||
|
|
||||||
// Notify our IM context
|
// Notify our IM context
|
||||||
c.gtk_im_context_focus_in(self.im_context);
|
c.gtk_im_context_focus_in(self.im_context);
|
||||||
|
|
||||||
|
@ -2,20 +2,33 @@ const Tab = @This();
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const assert = std.debug.assert;
|
||||||
const font = @import("../../font/main.zig");
|
const font = @import("../../font/main.zig");
|
||||||
const CoreSurface = @import("../../Surface.zig");
|
const CoreSurface = @import("../../Surface.zig");
|
||||||
const Paned = @import("Paned.zig");
|
const Paned = @import("Paned.zig");
|
||||||
|
const Parent = @import("parent.zig").Parent;
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
const log = std.log.scoped(.gtk);
|
||||||
|
|
||||||
const GHOSTTY_TAB = "ghostty_tab";
|
pub const GHOSTTY_TAB = "ghostty_tab";
|
||||||
|
|
||||||
const Child = union(enum) {
|
pub const Child = union(enum) {
|
||||||
surface: *Surface,
|
surface: *Surface,
|
||||||
paned: *Paned,
|
paned: *Paned,
|
||||||
|
|
||||||
|
empty,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn is_empty(self: Self) bool {
|
||||||
|
switch (self) {
|
||||||
|
Child.empty => return true,
|
||||||
|
else => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window: *Window,
|
window: *Window,
|
||||||
@ -35,6 +48,7 @@ pub fn create(alloc: Allocator, window: *Window, parent_: ?*CoreSurface) !*Tab {
|
|||||||
var tab = try alloc.create(Tab);
|
var tab = try alloc.create(Tab);
|
||||||
errdefer alloc.destroy(tab);
|
errdefer alloc.destroy(tab);
|
||||||
try tab.init(window, parent_);
|
try tab.init(window, parent_);
|
||||||
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
||||||
@ -48,8 +62,11 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Grab a surface allocation we'll need it later.
|
// Grab a surface allocation we'll need it later.
|
||||||
var surface = try self.app.core_app.alloc.create(Surface);
|
var surface = try window.app.core_app.alloc.create(Surface);
|
||||||
errdefer self.app.core_app.alloc.destroy(surface);
|
errdefer window.app.core_app.alloc.destroy(surface);
|
||||||
|
self.child = Child{ .surface = surface };
|
||||||
|
// TODO: this needs to change
|
||||||
|
self.focus_child = surface;
|
||||||
|
|
||||||
// Inherit the parent's font size if we are configured to.
|
// Inherit the parent's font size if we are configured to.
|
||||||
const font_size: ?font.face.DesiredSize = font_size: {
|
const font_size: ?font.face.DesiredSize = font_size: {
|
||||||
@ -60,25 +77,35 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
|||||||
|
|
||||||
// Build the tab label
|
// Build the tab label
|
||||||
const label_box_widget = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 0);
|
const label_box_widget = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
const label_box: *c.GtkBox = @ptrCast(label_box_widget);
|
const label_box = @as(*c.GtkBox, @ptrCast(label_box_widget));
|
||||||
const label_text_widget = c.gtk_label_new("Ghostty");
|
const label_text_widget = c.gtk_label_new("Ghostty");
|
||||||
const label_text: *c.GtkLabel = @ptrCast(label_text_widget);
|
const label_text: *c.GtkLabel = @ptrCast(label_text_widget);
|
||||||
self.label_text = label_text;
|
self.label_text = label_text;
|
||||||
c.gtk_box_append(label_box, label_text_widget);
|
c.gtk_box_append(label_box, label_text_widget);
|
||||||
const label_close_widget = c.gtk_button_new_from_icon_name("window-close");
|
const label_close_widget = c.gtk_button_new_from_icon_name("window-close");
|
||||||
const label_close: *c.GtkButton = @ptrCast(label_close_widget);
|
const label_close: *c.GtkButton = @ptrCast(label_close_widget);
|
||||||
c.gtk_button_has_frame(label_close, 0);
|
c.gtk_button_set_has_frame(label_close, 0);
|
||||||
c.gtk_box_append(label_box, label_close_widget);
|
c.gtk_box_append(label_box, label_close_widget);
|
||||||
self.close_button = label_close;
|
self.close_button = label_close;
|
||||||
|
|
||||||
_ = c.g_signal_connect_data(label_close, "clicked", c.G_CALLBACK(>kTabCloseClick), surface, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(label_close, "clicked", c.G_CALLBACK(>kTabCloseClick), self, null, c.G_CONNECT_DEFAULT);
|
||||||
|
|
||||||
// Wide style GTK tabs
|
// Wide style GTK tabs
|
||||||
if (self.app.config.@"gtk-wide-tabs") {
|
if (window.app.config.@"gtk-wide-tabs") {
|
||||||
c.gtk_widget_set_hexpand(label_box_widget, 1);
|
c.gtk_widget_set_hexpand(label_box_widget, 1);
|
||||||
c.gtk_widget_set_halign(label_box_widget, c.GTK_ALIGN_FILL);
|
c.gtk_widget_set_halign(label_box_widget, c.GTK_ALIGN_FILL);
|
||||||
c.gtk_widget_set_hexpand(label_text, 1);
|
c.gtk_widget_set_hexpand(label_text_widget, 1);
|
||||||
c.gtk_widget_set_halign(label_text, c.GTK_ALIGN_FILL);
|
c.gtk_widget_set_halign(label_text_widget, c.GTK_ALIGN_FILL);
|
||||||
|
|
||||||
|
// This ensures that tabs are always equal width. If they're too
|
||||||
|
// long, they'll be truncated with an ellipsis.
|
||||||
|
c.gtk_label_set_max_width_chars(@ptrCast(label_text), 1);
|
||||||
|
c.gtk_label_set_ellipsize(@ptrCast(label_text), c.PANGO_ELLIPSIZE_END);
|
||||||
|
|
||||||
|
// We need to set a minimum width so that at a certain point
|
||||||
|
// the notebook will have an arrow button rather than shrinking tabs
|
||||||
|
// to an unreadably small size.
|
||||||
|
c.gtk_widget_set_size_request(@ptrCast(label_text), 100, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const box_widget = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
const box_widget = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||||
@ -93,42 +120,86 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
|||||||
const gl_area = c.gtk_gl_area_new();
|
const gl_area = c.gtk_gl_area_new();
|
||||||
c.gtk_widget_set_hexpand(gl_area, 1);
|
c.gtk_widget_set_hexpand(gl_area, 1);
|
||||||
c.gtk_widget_set_vexpand(gl_area, 1);
|
c.gtk_widget_set_vexpand(gl_area, 1);
|
||||||
try surface.init(self.app, .{
|
try surface.init(window.app, .{
|
||||||
.window = self,
|
.window = window,
|
||||||
|
.tab = self,
|
||||||
|
.parent = .{ .tab = self },
|
||||||
.gl_area = @ptrCast(gl_area),
|
.gl_area = @ptrCast(gl_area),
|
||||||
.title_label = @ptrCast(label_text),
|
.title_label = @ptrCast(label_text),
|
||||||
.font_size = font_size,
|
.font_size = font_size,
|
||||||
});
|
});
|
||||||
errdefer surface.deinit();
|
errdefer surface.deinit();
|
||||||
|
|
||||||
c.gtk_box_pack_start(self.box, gl_area);
|
c.gtk_box_append(self.box, gl_area);
|
||||||
const page_idx = c.gtk_notebook_append_page(self.notebook, box_widget, label_box_widget);
|
const page_idx = c.gtk_notebook_append_page(window.notebook, box_widget, label_box_widget);
|
||||||
if (page_idx < 0) {
|
if (page_idx < 0) {
|
||||||
log.warn("failed to add page to notebook", .{});
|
log.warn("failed to add page to notebook", .{});
|
||||||
return error.GtkAppendPageFailed;
|
return error.GtkAppendPageFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab settings
|
// Tab settings
|
||||||
c.gtk_notebook_set_tab_reorderable(self.notebook, gl_area, 1);
|
c.gtk_notebook_set_tab_reorderable(window.notebook, gl_area, 1);
|
||||||
c.gtk_notebook_set_tab_detachable(self.notebook, gl_area, 1);
|
c.gtk_notebook_set_tab_detachable(window.notebook, gl_area, 1);
|
||||||
|
|
||||||
// If we have multiple tabs, show the tab bar.
|
// If we have multiple tabs, show the tab bar.
|
||||||
if (c.gtk_notebook_get_n_pages(self.notebook) > 1) {
|
if (c.gtk_notebook_get_n_pages(window.notebook) > 1) {
|
||||||
c.gtk_notebook_set_show_tabs(self.notebook, 1);
|
c.gtk_notebook_set_show_tabs(window.notebook, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the userdata of the close button so it points to this page.
|
// Set the userdata of the box to point to this tab.
|
||||||
c.g_object_set_data(@ptrCast(box_widget), GHOSTTY_TAB, self);
|
c.g_object_set_data(@ptrCast(box_widget), GHOSTTY_TAB, self);
|
||||||
|
|
||||||
// Switch to the new tab
|
// Switch to the new tab
|
||||||
c.gtk_notebook_set_current_page(self.notebook, page_idx);
|
c.gtk_notebook_set_current_page(window.notebook, page_idx);
|
||||||
|
|
||||||
// We need to grab focus after it is added to the window. When
|
// We need to grab focus after it is added to the window. When
|
||||||
// creating a window we want to always focus on the widget.
|
// creating a window we want to always focus on the widget.
|
||||||
_ = c.gtk_widget_grab_focus(box_widget);
|
const widget = @as(*c.GtkWidget, @ptrCast(gl_area));
|
||||||
|
_ = c.gtk_widget_grab_focus(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeChild(self: *Tab) void {
|
||||||
|
// Remove old child from box.
|
||||||
|
const widget = switch (self.child) {
|
||||||
|
.surface => |surface| @as(*c.GtkWidget, @ptrCast(surface.gl_area)),
|
||||||
|
.paned => |paned| @as(*c.GtkWidget, @ptrCast(@alignCast(paned.paned))),
|
||||||
|
.empty => return,
|
||||||
|
};
|
||||||
|
c.gtk_box_remove(self.box, widget);
|
||||||
|
self.child = .empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setChild(self: *Tab, newChild: Child) void {
|
||||||
|
const parent = Parent{ .tab = self };
|
||||||
|
|
||||||
|
switch (newChild) {
|
||||||
|
.surface => |surface| {
|
||||||
|
surface.setParent(parent);
|
||||||
|
const widget = @as(*c.GtkWidget, @ptrCast(surface.gl_area));
|
||||||
|
c.gtk_box_append(self.box, widget);
|
||||||
|
},
|
||||||
|
.paned => |paned| {
|
||||||
|
paned.parent = parent;
|
||||||
|
const widget = @as(*c.GtkWidget, @ptrCast(@alignCast(paned.paned)));
|
||||||
|
c.gtk_box_append(self.box, widget);
|
||||||
|
},
|
||||||
|
.empty => return,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.child = newChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setChildSurface(self: *Tab, surface: *Surface, gl_area: *c.GtkWidget) !void {
|
||||||
|
c.gtk_box_append(self.box, gl_area);
|
||||||
|
|
||||||
|
const parent = Parent{ .tab = self };
|
||||||
|
surface.setParent(parent);
|
||||||
|
|
||||||
|
self.child = .{ .surface = surface };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkTabCloseClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
fn gtkTabCloseClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||||
_ = ud;
|
const tab: *Tab = @ptrCast(@alignCast(ud));
|
||||||
// todo
|
_ = tab;
|
||||||
|
log.info("tab close click\n", .{});
|
||||||
}
|
}
|
||||||
|
@ -8,18 +8,18 @@ const Allocator = std.mem.Allocator;
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../../config.zig");
|
||||||
const font = @import("../../font/main.zig");
|
const font = @import("../../font/main.zig");
|
||||||
|
const input = @import("../../input.zig");
|
||||||
const CoreSurface = @import("../../Surface.zig");
|
const CoreSurface = @import("../../Surface.zig");
|
||||||
|
|
||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const Paned = @import("Paned.zig");
|
const Paned = @import("Paned.zig");
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
|
const Tab = @import("Tab.zig");
|
||||||
const icon = @import("icon.zig");
|
const icon = @import("icon.zig");
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
const log = std.log.scoped(.gtk);
|
||||||
|
|
||||||
const GL_AREA_SURFACE = "gl_area_surface";
|
|
||||||
|
|
||||||
app: *App,
|
app: *App,
|
||||||
|
|
||||||
/// Our window
|
/// Our window
|
||||||
@ -32,6 +32,8 @@ notebook: *c.GtkNotebook,
|
|||||||
/// pointer to this because GTK can use it at any time.
|
/// pointer to this because GTK can use it at any time.
|
||||||
icon: icon.Icon,
|
icon: icon.Icon,
|
||||||
|
|
||||||
|
tabs: std.ArrayListUnmanaged(*Tab),
|
||||||
|
|
||||||
pub fn create(alloc: Allocator, app: *App) !*Window {
|
pub fn create(alloc: Allocator, app: *App) !*Window {
|
||||||
// Allocate a fixed pointer for our window. We try to minimize
|
// Allocate a fixed pointer for our window. We try to minimize
|
||||||
// allocations but windows and other GUI requirements are so minimal
|
// allocations but windows and other GUI requirements are so minimal
|
||||||
@ -53,8 +55,13 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
.icon = undefined,
|
.icon = undefined,
|
||||||
.window = undefined,
|
.window = undefined,
|
||||||
.notebook = undefined,
|
.notebook = undefined,
|
||||||
|
.tabs = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var tabs: std.ArrayListUnmanaged(*Tab) = .{};
|
||||||
|
errdefer tabs.deinit(app.core_app.alloc);
|
||||||
|
self.tabs = tabs;
|
||||||
|
|
||||||
// Create the window
|
// Create the window
|
||||||
const window = c.gtk_application_window_new(app.app);
|
const window = c.gtk_application_window_new(app.app);
|
||||||
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
||||||
@ -182,105 +189,48 @@ fn initActions(self: *Window) void {
|
|||||||
|
|
||||||
pub fn deinit(self: *Window) void {
|
pub fn deinit(self: *Window) void {
|
||||||
self.icon.deinit(self.app);
|
self.icon.deinit(self.app);
|
||||||
|
for (self.tabs.items) |tab| self.app.core_app.alloc.destroy(tab);
|
||||||
|
self.tabs.deinit(self.app.core_app.alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new tab to this window.
|
/// Add a new tab to this window.
|
||||||
pub fn newTab(self: *Window, parent_: ?*CoreSurface) !void {
|
pub fn newTab(self: *Window, parentSurface: ?*CoreSurface) !void {
|
||||||
// Grab a surface allocation we'll need it later.
|
const tab = try Tab.create(self.app.core_app.alloc, self, parentSurface);
|
||||||
var surface = try self.app.core_app.alloc.create(Surface);
|
try self.tabs.append(self.app.core_app.alloc, tab);
|
||||||
errdefer self.app.core_app.alloc.destroy(surface);
|
|
||||||
|
|
||||||
// Inherit the parent's font size if we are configured to.
|
log.info("\n\n\nnewTab. New tabs len={}\n", .{self.tabs.items.len});
|
||||||
const font_size: ?font.face.DesiredSize = font_size: {
|
// TODO: When this is triggered through a GTK action, the new surface
|
||||||
if (!self.app.config.@"window-inherit-font-size") break :font_size null;
|
// redraws correctly. When it's triggered through keyboard shortcuts, it
|
||||||
const parent = parent_ orelse break :font_size null;
|
// does not (cursor doesn't blink).
|
||||||
break :font_size parent.font_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build our tab label
|
|
||||||
const label_box_widget = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 0);
|
|
||||||
const label_box = @as(*c.GtkBox, @ptrCast(label_box_widget));
|
|
||||||
const label_text = c.gtk_label_new("Ghostty");
|
|
||||||
c.gtk_box_append(label_box, label_text);
|
|
||||||
|
|
||||||
// Wide style GTK tabs
|
|
||||||
if (self.app.config.@"gtk-wide-tabs") {
|
|
||||||
c.gtk_widget_set_hexpand(label_box_widget, 1);
|
|
||||||
c.gtk_widget_set_halign(label_box_widget, c.GTK_ALIGN_FILL);
|
|
||||||
c.gtk_widget_set_hexpand(label_text, 1);
|
|
||||||
c.gtk_widget_set_halign(label_text, c.GTK_ALIGN_FILL);
|
|
||||||
|
|
||||||
// This ensures that tabs are always equal width. If they're too
|
|
||||||
// long, they'll be truncated with an ellipsis.
|
|
||||||
c.gtk_label_set_max_width_chars(@ptrCast(label_text), 1);
|
|
||||||
c.gtk_label_set_ellipsize(@ptrCast(label_text), c.PANGO_ELLIPSIZE_END);
|
|
||||||
|
|
||||||
// We need to set a minimum width so that at a certain point
|
|
||||||
// the notebook will have an arrow button rather than shrinking tabs
|
|
||||||
// to an unreadably small size.
|
|
||||||
c.gtk_widget_set_size_request(@ptrCast(label_text), 100, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const label_close_widget = c.gtk_button_new_from_icon_name("window-close");
|
|
||||||
const label_close = @as(*c.GtkButton, @ptrCast(label_close_widget));
|
|
||||||
c.gtk_button_set_has_frame(label_close, 0);
|
|
||||||
c.gtk_box_append(label_box, label_close_widget);
|
|
||||||
_ = c.g_signal_connect_data(label_close, "clicked", c.G_CALLBACK(>kTabCloseClick), surface, null, c.G_CONNECT_DEFAULT);
|
|
||||||
|
|
||||||
// Initialize the GtkGLArea and attach it to our surface.
|
|
||||||
// The surface starts in the "unrealized" state because we have to
|
|
||||||
// wait for the "realize" callback from GTK to know that the OpenGL
|
|
||||||
// context is ready. See Surface docs for more info.
|
|
||||||
const gl_area = c.gtk_gl_area_new();
|
|
||||||
c.gtk_widget_set_cursor_from_name(gl_area, "text");
|
|
||||||
try surface.init(self.app, .{
|
|
||||||
.window = self,
|
|
||||||
.gl_area = @ptrCast(gl_area),
|
|
||||||
.title_label = @ptrCast(label_text),
|
|
||||||
.font_size = font_size,
|
|
||||||
.parent = parent_ != null,
|
|
||||||
});
|
|
||||||
errdefer surface.deinit();
|
|
||||||
|
|
||||||
// Add the notebook page (create tab). We create the tab after our
|
|
||||||
// current selected tab if we have one.
|
|
||||||
const page_idx = c.gtk_notebook_insert_page(
|
|
||||||
self.notebook,
|
|
||||||
gl_area,
|
|
||||||
label_box_widget,
|
|
||||||
c.gtk_notebook_get_current_page(self.notebook) + 1,
|
|
||||||
);
|
|
||||||
if (page_idx < 0) {
|
|
||||||
log.warn("failed to add surface to notebook", .{});
|
|
||||||
return error.GtkAppendPageFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tab settings
|
|
||||||
c.gtk_notebook_set_tab_reorderable(self.notebook, gl_area, 1);
|
|
||||||
c.gtk_notebook_set_tab_detachable(self.notebook, gl_area, 1);
|
|
||||||
|
|
||||||
// If we have multiple tabs, show the tab bar.
|
|
||||||
if (c.gtk_notebook_get_n_pages(self.notebook) > 1) {
|
|
||||||
c.gtk_notebook_set_show_tabs(self.notebook, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the userdata of the close button so it points to this page.
|
|
||||||
c.g_object_set_data(@ptrCast(gl_area), GL_AREA_SURFACE, surface);
|
|
||||||
|
|
||||||
// Switch to the new tab
|
|
||||||
c.gtk_notebook_set_current_page(self.notebook, page_idx);
|
|
||||||
|
|
||||||
// We need to grab focus after it is added to the window. When
|
|
||||||
// creating a window we want to always focus on the widget.
|
|
||||||
const widget = @as(*c.GtkWidget, @ptrCast(gl_area));
|
|
||||||
_ = c.gtk_widget_grab_focus(widget);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close the tab for the given notebook page. This will automatically
|
/// Close the tab for the given notebook page. This will automatically
|
||||||
/// handle closing the window if there are no more tabs.
|
/// handle closing the window if there are no more tabs.
|
||||||
fn closeTab(self: *Window, page: *c.GtkNotebookPage) void {
|
fn closeTab(self: *Window, page: *c.GtkNotebookPage) void {
|
||||||
// Remove the page
|
// Find page and tab which we're closing
|
||||||
const page_idx = getNotebookPageIndex(page);
|
const page_idx = getNotebookPageIndex(page);
|
||||||
|
const page_widget = c.gtk_notebook_get_nth_page(self.notebook, page_idx);
|
||||||
|
const tab: *Tab = @ptrCast(@alignCast(
|
||||||
|
c.g_object_get_data(@ptrCast(page_widget), Tab.GHOSTTY_TAB) orelse return,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Remove the tab from our stored tabs.
|
||||||
|
const tab_idx = for (self.tabs.items, 0..) |t, i| {
|
||||||
|
if (t == tab) break i;
|
||||||
|
} else null;
|
||||||
|
// TODO: Shrink capacity?
|
||||||
|
if (tab_idx) |idx| {
|
||||||
|
_ = self.tabs.orderedRemove(idx);
|
||||||
|
} else {
|
||||||
|
log.info("tab of page {} not found in managed tabs list\n", .{page_idx});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Deallocate the tab
|
||||||
|
self.app.core_app.alloc.destroy(tab);
|
||||||
|
|
||||||
|
log.info("\n\n\ncloseTab. New tabs len={}\n", .{self.tabs.items.len});
|
||||||
|
|
||||||
|
// Now remove the page
|
||||||
c.gtk_notebook_remove_page(self.notebook, page_idx);
|
c.gtk_notebook_remove_page(self.notebook, page_idx);
|
||||||
|
|
||||||
const remaining = c.gtk_notebook_get_n_pages(self.notebook);
|
const remaining = c.gtk_notebook_get_n_pages(self.notebook);
|
||||||
@ -302,8 +252,75 @@ fn closeTab(self: *Window, page: *c.GtkNotebookPage) void {
|
|||||||
pub fn closeSurface(self: *Window, surface: *Surface) void {
|
pub fn closeSurface(self: *Window, surface: *Surface) void {
|
||||||
assert(surface.window == self);
|
assert(surface.window == self);
|
||||||
|
|
||||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(surface.gl_area)) orelse return;
|
const alloc = surface.app.core_app.alloc;
|
||||||
|
|
||||||
|
switch (surface.parent) {
|
||||||
|
.tab => {
|
||||||
|
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(surface.tab.box)) orelse return;
|
||||||
self.closeTab(page);
|
self.closeTab(page);
|
||||||
|
},
|
||||||
|
.paned => |paned_tuple| {
|
||||||
|
const paned = paned_tuple[0];
|
||||||
|
const position = paned_tuple[1];
|
||||||
|
|
||||||
|
// TODO: Do we need this?
|
||||||
|
surface.setParent(.none);
|
||||||
|
|
||||||
|
const sibling = switch (position) {
|
||||||
|
.start => .{
|
||||||
|
switch (paned.child2) {
|
||||||
|
.surface => |s| s,
|
||||||
|
else => return,
|
||||||
|
},
|
||||||
|
c.gtk_paned_get_end_child(paned.paned),
|
||||||
|
},
|
||||||
|
.end => .{
|
||||||
|
switch (paned.child1) {
|
||||||
|
.surface => |s| s,
|
||||||
|
else => return,
|
||||||
|
},
|
||||||
|
c.gtk_paned_get_start_child(paned.paned),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// TODO: Use destructuring syntax once it doesn't break ZLS
|
||||||
|
const sibling_surface = sibling[0];
|
||||||
|
const sibling_widget = sibling[1];
|
||||||
|
|
||||||
|
// Keep explicit reference to sibling's gl_area, so it's not
|
||||||
|
// destroyed when we remove it from GtkPaned.
|
||||||
|
const sibling_object: *c.GObject = @ptrCast(sibling_widget);
|
||||||
|
_ = c.g_object_ref(sibling_object);
|
||||||
|
defer c.g_object_unref(sibling_object);
|
||||||
|
|
||||||
|
// Remove children and kill Paned.
|
||||||
|
paned.removeChild1();
|
||||||
|
paned.removeChild2();
|
||||||
|
defer alloc.destroy(paned);
|
||||||
|
|
||||||
|
// Remove children from Paned we were part of.
|
||||||
|
switch (paned.parent) {
|
||||||
|
.tab => |tab| {
|
||||||
|
// If parent of Paned we belong to is a tab, we can
|
||||||
|
// replace the child with the other surface
|
||||||
|
tab.removeChild();
|
||||||
|
|
||||||
|
tab.setChild(.{ .surface = sibling_surface });
|
||||||
|
// try tab.setChildSurface(sibling_surface, sibling_widget);
|
||||||
|
},
|
||||||
|
.paned => |paned_paned| {
|
||||||
|
log.info("paned is nested, parent is paned. position={}", .{paned_paned[1]});
|
||||||
|
},
|
||||||
|
.none => {
|
||||||
|
log.info("paned has no parent", .{});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// alloc.destroy(paned);
|
||||||
|
},
|
||||||
|
.none => {
|
||||||
|
log.info("no parent, dude?!", .{});
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this window has any tabs.
|
/// Returns true if this window has any tabs.
|
||||||
@ -313,7 +330,7 @@ pub fn hasTabs(self: *const Window) bool {
|
|||||||
|
|
||||||
/// Go to the previous tab for a surface.
|
/// Go to the previous tab for a surface.
|
||||||
pub fn gotoPreviousTab(self: *Window, surface: *Surface) void {
|
pub fn gotoPreviousTab(self: *Window, surface: *Surface) void {
|
||||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(surface.gl_area)) orelse return;
|
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(surface.tab.box)) orelse return;
|
||||||
const page_idx = getNotebookPageIndex(page);
|
const page_idx = getNotebookPageIndex(page);
|
||||||
|
|
||||||
// The next index is the previous or we wrap around.
|
// The next index is the previous or we wrap around.
|
||||||
@ -331,7 +348,7 @@ pub fn gotoPreviousTab(self: *Window, surface: *Surface) void {
|
|||||||
|
|
||||||
/// Go to the next tab for a surface.
|
/// Go to the next tab for a surface.
|
||||||
pub fn gotoNextTab(self: *Window, surface: *Surface) void {
|
pub fn gotoNextTab(self: *Window, surface: *Surface) void {
|
||||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(surface.gl_area)) orelse return;
|
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(surface.tab.box)) orelse return;
|
||||||
const page_idx = getNotebookPageIndex(page);
|
const page_idx = getNotebookPageIndex(page);
|
||||||
const max = c.gtk_notebook_get_n_pages(self.notebook) -| 1;
|
const max = c.gtk_notebook_get_n_pages(self.notebook) -| 1;
|
||||||
const next_idx = if (page_idx < max) page_idx + 1 else 0;
|
const next_idx = if (page_idx < max) page_idx + 1 else 0;
|
||||||
@ -365,13 +382,12 @@ pub fn toggleFullscreen(self: *Window, _: configpkg.NonNativeFullscreen) void {
|
|||||||
/// Grabs focus on the currently selected tab.
|
/// Grabs focus on the currently selected tab.
|
||||||
fn focusCurrentTab(self: *Window) void {
|
fn focusCurrentTab(self: *Window) void {
|
||||||
const page_idx = c.gtk_notebook_get_current_page(self.notebook);
|
const page_idx = c.gtk_notebook_get_current_page(self.notebook);
|
||||||
const widget = c.gtk_notebook_get_nth_page(self.notebook, page_idx);
|
const page = c.gtk_notebook_get_nth_page(self.notebook, page_idx);
|
||||||
_ = c.gtk_widget_grab_focus(widget);
|
const tab: *Tab = @ptrCast(@alignCast(
|
||||||
}
|
c.g_object_get_data(@ptrCast(page), Tab.GHOSTTY_TAB) orelse return,
|
||||||
|
));
|
||||||
fn gtkTabCloseClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
const gl_area = @as(*c.GtkWidget, @ptrCast(tab.focus_child.gl_area));
|
||||||
const surface: *Surface = @ptrCast(@alignCast(ud));
|
_ = c.gtk_widget_grab_focus(gl_area);
|
||||||
surface.core_surface.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab
|
// Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab
|
||||||
@ -424,10 +440,11 @@ fn gtkNotebookCreateWindow(
|
|||||||
page: *c.GtkWidget,
|
page: *c.GtkWidget,
|
||||||
ud: ?*anyopaque,
|
ud: ?*anyopaque,
|
||||||
) callconv(.C) ?*c.GtkNotebook {
|
) callconv(.C) ?*c.GtkNotebook {
|
||||||
// The surface for the page is stored in the widget data.
|
// The tab for the page is stored in the widget data.
|
||||||
const surface: *Surface = @ptrCast(@alignCast(
|
const tab: *Tab = @ptrCast(@alignCast(
|
||||||
c.g_object_get_data(@ptrCast(page), GL_AREA_SURFACE) orelse return null,
|
c.g_object_get_data(@ptrCast(page), Tab.GHOSTTY_TAB) orelse return null,
|
||||||
));
|
));
|
||||||
|
const surface: *Surface = tab.focus_child;
|
||||||
|
|
||||||
const self = userdataSelf(ud.?);
|
const self = userdataSelf(ud.?);
|
||||||
const alloc = self.app.core_app.alloc;
|
const alloc = self.app.core_app.alloc;
|
||||||
@ -438,9 +455,10 @@ fn gtkNotebookCreateWindow(
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to update our surface to point to the new window so that
|
// We need to update our surface to point to the new window and tab so that
|
||||||
// events such as new tab go to the right window.
|
// events such as new tab go to the right window.
|
||||||
surface.window = window;
|
surface.window = window;
|
||||||
|
surface.tab = window.tabs.items[window.tabs.items.len - 1];
|
||||||
|
|
||||||
return window.notebook;
|
return window.notebook;
|
||||||
}
|
}
|
||||||
@ -460,6 +478,7 @@ fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug("WE ARE HERE", .{});
|
||||||
// Setup our basic message
|
// Setup our basic message
|
||||||
const alert = c.gtk_message_dialog_new(
|
const alert = c.gtk_message_dialog_new(
|
||||||
self.window,
|
self.window,
|
||||||
@ -603,10 +622,10 @@ fn gtkActionToggleInspector(
|
|||||||
fn actionSurface(self: *Window) ?*CoreSurface {
|
fn actionSurface(self: *Window) ?*CoreSurface {
|
||||||
const page_idx = c.gtk_notebook_get_current_page(self.notebook);
|
const page_idx = c.gtk_notebook_get_current_page(self.notebook);
|
||||||
const page = c.gtk_notebook_get_nth_page(self.notebook, page_idx);
|
const page = c.gtk_notebook_get_nth_page(self.notebook, page_idx);
|
||||||
const surface: *Surface = @ptrCast(@alignCast(
|
const tab: *Tab = @ptrCast(@alignCast(
|
||||||
c.g_object_get_data(@ptrCast(page), GL_AREA_SURFACE) orelse return null,
|
c.g_object_get_data(@ptrCast(page), Tab.GHOSTTY_TAB) orelse return null,
|
||||||
));
|
));
|
||||||
return &surface.core_surface;
|
return &tab.focus_child.core_surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn userdataSelf(ud: *anyopaque) *Window {
|
fn userdataSelf(ud: *anyopaque) *Window {
|
||||||
|
Reference in New Issue
Block a user