mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
apprt/gtk: working on some reorg
This commit is contained in:
@ -56,7 +56,8 @@ pub fn init(self: *Paned, sibling: *Surface, direction: input.SplitDirection) !v
|
||||
const gtk_paned: *c.GtkPaned = @ptrCast(paned);
|
||||
self.paned = gtk_paned;
|
||||
|
||||
const surface = try sibling.tab.newSurface(&sibling.core_surface);
|
||||
const tab = sibling.container.tab().?; // TODO
|
||||
const surface = try tab.newSurface(&sibling.core_surface);
|
||||
surface.setParent(.{ .paned = .{ self, .end } });
|
||||
|
||||
self.addChild1(.{ .surface = sibling });
|
||||
|
@ -55,6 +55,38 @@ pub const Options = struct {
|
||||
parentSurface: bool = false,
|
||||
};
|
||||
|
||||
/// The container that this surface is directly attached to.
|
||||
pub const Container = union(enum) {
|
||||
/// The surface is not currently attached to anything. This means
|
||||
/// that the GLArea has been created and potentially initialized
|
||||
/// but the widget is currently floating and not part of any parent.
|
||||
none: void,
|
||||
|
||||
/// Directly attached to a tab. (i.e. no splits)
|
||||
tab_: *Tab,
|
||||
|
||||
/// A split within a split hierarchy.
|
||||
paned: *Paned,
|
||||
|
||||
/// Returns the window that this surface is attached to.
|
||||
pub fn window(self: Container) ?*Window {
|
||||
return switch (self) {
|
||||
.none => null,
|
||||
.tab_ => |v| v.window,
|
||||
else => @panic("TODO"),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the tab container if it exists.
|
||||
pub fn tab(self: Container) ?*Tab {
|
||||
return switch (self) {
|
||||
.none => null,
|
||||
.tab_ => |v| v,
|
||||
else => @panic("TODO"),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Where the title of this surface will go.
|
||||
const Title = union(enum) {
|
||||
none: void,
|
||||
@ -69,15 +101,16 @@ realized: bool = false,
|
||||
/// See Options.parentSurface
|
||||
parentSurface: bool = false,
|
||||
|
||||
/// The GUI container that this surface has been attached to. This
|
||||
/// dictates some behaviors such as new splits, etc.
|
||||
container: Container = .{ .none = {} },
|
||||
|
||||
/// The app we're part of
|
||||
app: *App,
|
||||
|
||||
/// The window we're part of
|
||||
window: *Window,
|
||||
|
||||
/// The tab we're part of
|
||||
tab: *Tab,
|
||||
|
||||
/// The parent we belong to
|
||||
parent: Parent,
|
||||
|
||||
@ -177,7 +210,7 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
||||
self.* = .{
|
||||
.app = app,
|
||||
.window = opts.window,
|
||||
.tab = opts.tab,
|
||||
.container = .{ .tab_ = opts.tab },
|
||||
.parent = opts.parent,
|
||||
.gl_area = opts.gl_area,
|
||||
.title = if (opts.title_label) |label| .{
|
||||
@ -456,8 +489,9 @@ pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.Surfac
|
||||
}
|
||||
|
||||
pub fn grabFocus(self: *Surface) void {
|
||||
if (self.container.tab()) |tab| tab.focus_child = self;
|
||||
|
||||
self.updateTitleLabels();
|
||||
self.tab.focus_child = self;
|
||||
const widget = @as(*c.GtkWidget, @ptrCast(self.gl_area));
|
||||
_ = c.gtk_widget_grab_focus(widget);
|
||||
}
|
||||
@ -859,7 +893,7 @@ fn gtkMouseDown(
|
||||
// If we don't have focus, grab it.
|
||||
const gl_widget = @as(*c.GtkWidget, @ptrCast(self.gl_area));
|
||||
if (c.gtk_widget_has_focus(gl_widget) == 0) {
|
||||
self.tab.focus_child = self;
|
||||
if (self.container.tab()) |tab| tab.focus_child = self;
|
||||
_ = c.gtk_widget_grab_focus(gl_widget);
|
||||
|
||||
// If we have siblings, we also update the title, since it means
|
||||
|
@ -1,3 +1,6 @@
|
||||
/// The state associated with a single tab in the window.
|
||||
///
|
||||
/// A tab can contain one or more terminals due to splits.
|
||||
const Tab = @This();
|
||||
|
||||
const std = @import("std");
|
||||
@ -20,7 +23,6 @@ pub const GHOSTTY_TAB = "ghostty_tab";
|
||||
|
||||
window: *Window,
|
||||
label_text: *c.GtkLabel,
|
||||
close_button: *c.GtkButton,
|
||||
// We'll put our children into this box instead of packing them directly, so
|
||||
// that we can send the box into `c.g_signal_connect_data` for the close button
|
||||
box: *c.GtkBox,
|
||||
@ -38,11 +40,12 @@ pub fn create(alloc: Allocator, window: *Window, parent_: ?*CoreSurface) !*Tab {
|
||||
return tab;
|
||||
}
|
||||
|
||||
/// Initialize the tab, create a surface, and add it to the window. "self"
|
||||
/// needs to be a stable pointer, since it is used for GTK events.
|
||||
pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
||||
self.* = .{
|
||||
.window = window,
|
||||
.label_text = undefined,
|
||||
.close_button = undefined,
|
||||
.box = undefined,
|
||||
.child = undefined,
|
||||
.focus_child = undefined,
|
||||
@ -56,13 +59,11 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
||||
c.gtk_box_append(label_box, label_text_widget);
|
||||
self.label_text = label_text;
|
||||
|
||||
// Build the close button for the tab
|
||||
const label_close_widget = c.gtk_button_new_from_icon_name("window-close");
|
||||
const label_close: *c.GtkButton = @ptrCast(label_close_widget);
|
||||
c.gtk_button_set_has_frame(label_close, 0);
|
||||
c.gtk_box_append(label_box, label_close_widget);
|
||||
self.close_button = label_close;
|
||||
|
||||
_ = c.g_signal_connect_data(label_close, "clicked", c.G_CALLBACK(>kTabCloseClick), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// Wide style GTK tabs
|
||||
if (window.app.config.@"gtk-wide-tabs") {
|
||||
@ -88,11 +89,10 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
||||
c.gtk_widget_set_vexpand(box_widget, 1);
|
||||
self.box = @ptrCast(box_widget);
|
||||
|
||||
// Create the Surface
|
||||
// Create the initial surface since all tabs start as a single non-split
|
||||
const surface = try self.newSurface(parent_);
|
||||
errdefer surface.deinit();
|
||||
|
||||
self.child = Child{ .surface = surface };
|
||||
self.child = .{ .surface = surface };
|
||||
|
||||
// Add Surface to the Tab
|
||||
const gl_area_widget = @as(*c.GtkWidget, @ptrCast(surface.gl_area));
|
||||
@ -123,6 +123,9 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
||||
// Set the userdata of the box to point to this tab.
|
||||
c.g_object_set_data(@ptrCast(box_widget), GHOSTTY_TAB, self);
|
||||
|
||||
// Attach all events
|
||||
_ = c.g_signal_connect_data(label_close, "clicked", c.G_CALLBACK(>kTabCloseClick), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// Switch to the new tab
|
||||
c.gtk_notebook_set_current_page(window.notebook, page_idx);
|
||||
|
||||
@ -131,6 +134,17 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
||||
surface.grabFocus();
|
||||
}
|
||||
|
||||
/// Deinits tab by deiniting child if child is Paned.
|
||||
pub fn deinit(self: *Tab) void {
|
||||
switch (self.child) {
|
||||
.none, .surface => return,
|
||||
.paned => |paned| {
|
||||
paned.deinit(self.window.app.core_app.alloc);
|
||||
self.window.app.core_app.alloc.destroy(paned);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates and initializes a new Surface, but doesn't add it to the Tab yet.
|
||||
/// Can also be added to a Paned.
|
||||
pub fn newSurface(self: *Tab, parent_: ?*CoreSurface) !*Surface {
|
||||
@ -206,17 +220,6 @@ pub fn setChild(self: *Tab, child: Child) void {
|
||||
self.child = child;
|
||||
}
|
||||
|
||||
/// Deinits tab by deiniting child if child is Paned.
|
||||
pub fn deinit(self: *Tab) void {
|
||||
switch (self.child) {
|
||||
.none, .surface => return,
|
||||
.paned => |paned| {
|
||||
paned.deinit(self.window.app.core_app.alloc);
|
||||
self.window.app.core_app.alloc.destroy(paned);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn gtkTabCloseClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
const tab: *Tab = @ptrCast(@alignCast(ud));
|
||||
const window = tab.window;
|
||||
|
@ -1,4 +1,8 @@
|
||||
/// A Window is a single, real GTK window.
|
||||
/// A Window is a single, real GTK window that holds terminal surfaces.
|
||||
///
|
||||
/// A Window always contains a notebook (what GTK calls a tabbed container)
|
||||
/// even while no tabs are in use, because a notebook without a tab bar has
|
||||
/// no visible UI chrome.
|
||||
const Window = @This();
|
||||
|
||||
const std = @import("std");
|
||||
@ -327,7 +331,12 @@ pub fn hasTabs(self: *const Window) bool {
|
||||
|
||||
/// Go to the previous tab for a surface.
|
||||
pub fn gotoPreviousTab(self: *Window, surface: *Surface) void {
|
||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(surface.tab.box)) orelse return;
|
||||
const tab = surface.container.tab() orelse {
|
||||
log.info("surface is not attached to a tab bar, cannot navigate", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(tab.box)) orelse return;
|
||||
const page_idx = getNotebookPageIndex(page);
|
||||
|
||||
// The next index is the previous or we wrap around.
|
||||
@ -345,7 +354,12 @@ pub fn gotoPreviousTab(self: *Window, surface: *Surface) void {
|
||||
|
||||
/// Go to the next tab for a surface.
|
||||
pub fn gotoNextTab(self: *Window, surface: *Surface) void {
|
||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(surface.tab.box)) orelse return;
|
||||
const tab = surface.container.tab() orelse {
|
||||
log.info("surface is not attached to a tab bar, cannot navigate", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(tab.box)) orelse return;
|
||||
const page_idx = getNotebookPageIndex(page);
|
||||
const max = c.gtk_notebook_get_n_pages(self.notebook) -| 1;
|
||||
const next_idx = if (page_idx < max) page_idx + 1 else 0;
|
||||
|
Reference in New Issue
Block a user