macos: set window resizeIncrements when cell size changes

The resizeIncrements property is only modified when the cell size of the
focused window changes. If two splits have the same cell size then the
property is not modified when focusing between the two splits.
This commit is contained in:
Gregory Anders
2023-10-24 21:18:40 -05:00
parent be46bea40f
commit 2ee80a52df
11 changed files with 67 additions and 31 deletions

View File

@ -337,6 +337,7 @@ typedef void (*ghostty_runtime_goto_tab_cb)(void *, int32_t);
typedef void (*ghostty_runtime_toggle_fullscreen_cb)(void *, ghostty_non_native_fullscreen_e); typedef void (*ghostty_runtime_toggle_fullscreen_cb)(void *, ghostty_non_native_fullscreen_e);
typedef void (*ghostty_runtime_set_initial_window_size_cb)(void *, uint32_t, uint32_t); typedef void (*ghostty_runtime_set_initial_window_size_cb)(void *, uint32_t, uint32_t);
typedef void (*ghostty_runtime_render_inspector_cb)(void *); typedef void (*ghostty_runtime_render_inspector_cb)(void *);
typedef void (*ghostty_runtime_set_cell_size_cb)(void *, uint32_t, uint32_t);
typedef struct { typedef struct {
void *userdata; void *userdata;
@ -359,6 +360,7 @@ typedef struct {
ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb; ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb;
ghostty_runtime_set_initial_window_size_cb set_initial_window_size_cb; ghostty_runtime_set_initial_window_size_cb set_initial_window_size_cb;
ghostty_runtime_render_inspector_cb render_inspector_cb; ghostty_runtime_render_inspector_cb render_inspector_cb;
ghostty_runtime_set_cell_size_cb set_cell_size_cb;
} ghostty_runtime_config_s; } ghostty_runtime_config_s;
//------------------------------------------------------------------- //-------------------------------------------------------------------

View File

@ -38,7 +38,6 @@
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; }; A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; }; A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; };
A5FECBD729D1FC3900022361 /* PrimaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FECBD629D1FC3900022361 /* PrimaryView.swift */; }; A5FECBD729D1FC3900022361 /* PrimaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FECBD629D1FC3900022361 /* PrimaryView.swift */; };
A5FECBD929D2010400022361 /* WindowAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FECBD829D2010400022361 /* WindowAccessor.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -76,7 +75,6 @@
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; }; A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
A5FEB2FF2ABB69450068369E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; }; A5FEB2FF2ABB69450068369E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
A5FECBD629D1FC3900022361 /* PrimaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryView.swift; sourceTree = "<group>"; }; A5FECBD629D1FC3900022361 /* PrimaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryView.swift; sourceTree = "<group>"; };
A5FECBD829D2010400022361 /* WindowAccessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowAccessor.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -120,7 +118,6 @@
A5CEAFFE29C2410700646FDA /* Backport.swift */, A5CEAFFE29C2410700646FDA /* Backport.swift */,
8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */, 8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */,
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */, A59FB5D02AE0DEA7009128F3 /* MetalView.swift */,
A5FECBD829D2010400022361 /* WindowAccessor.swift */,
A5CEAFDA29B8005900646FDA /* SplitView */, A5CEAFDA29B8005900646FDA /* SplitView */,
); );
path = Helpers; path = Helpers;
@ -313,7 +310,6 @@
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */, A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */, A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */,
A55685E029A03A9F004303CE /* AppError.swift in Sources */, A55685E029A03A9F004303CE /* AppError.swift in Sources */,
A5FECBD929D2010400022361 /* WindowAccessor.swift in Sources */,
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */, A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
85102A1C2A6E32890084AB3E /* PrimaryWindowController.swift in Sources */, 85102A1C2A6E32890084AB3E /* PrimaryWindowController.swift in Sources */,
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */, A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,

View File

@ -16,7 +16,7 @@ struct PrimaryView: View {
// We need access to our window to know if we're the key window to determine // We need access to our window to know if we're the key window to determine
// if we show the quit confirmation or not. // if we show the quit confirmation or not.
@State private var window: NSWindow? var window: NSWindow
// This handles non-native fullscreen // This handles non-native fullscreen
@State private var fullScreen = FullScreenHandler() @State private var fullScreen = FullScreenHandler()
@ -27,6 +27,7 @@ struct PrimaryView: View {
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface @FocusedValue(\.ghosttySurfaceView) private var focusedSurface
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle
@FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit @FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit
@FocusedValue(\.ghosttySurfaceCellSize) private var cellSize
// The title for our window // The title for our window
private var title: String { private var title: String {
@ -68,7 +69,6 @@ struct PrimaryView: View {
Ghostty.TerminalSplit(onClose: Self.closeWindow, baseConfig: self.baseConfig) Ghostty.TerminalSplit(onClose: Self.closeWindow, baseConfig: self.baseConfig)
.ghosttyApp(ghostty.app!) .ghosttyApp(ghostty.app!)
.ghosttyConfig(ghostty.config!) .ghosttyConfig(ghostty.config!)
.background(WindowAccessor(window: $window))
.onReceive(gotoTab) { onGotoTab(notification: $0) } .onReceive(gotoTab) { onGotoTab(notification: $0) }
.onReceive(toggleFullscreen) { onToggleFullscreen(notification: $0) } .onReceive(toggleFullscreen) { onToggleFullscreen(notification: $0) }
.focused($focused) .focused($focused)
@ -79,8 +79,11 @@ struct PrimaryView: View {
.onChange(of: title) { newValue in .onChange(of: title) { newValue in
// We need to handle this manually because we are using AppKit lifecycle // We need to handle this manually because we are using AppKit lifecycle
// so navigationTitle no longer works. // so navigationTitle no longer works.
guard let window = self.window else { return } self.window.title = newValue
window.title = newValue }
.onChange(of: cellSize) { newValue in
guard let size = newValue else { return }
self.window.contentResizeIncrements = size
} }
} }
} }
@ -95,8 +98,7 @@ struct PrimaryView: View {
// Notification center indiscriminately sends to every subscriber (makes sense) // Notification center indiscriminately sends to every subscriber (makes sense)
// but we only want to process this once. In order to process it once lets only // but we only want to process this once. In order to process it once lets only
// handle it if we're the focused window. // handle it if we're the focused window.
guard let window = self.window else { return } guard self.window.isKeyWindow else { return }
guard window.isKeyWindow else { return }
// Get the tab index from the notification // Get the tab index from the notification
guard let tabIndexAny = notification.userInfo?[Ghostty.Notification.GotoTabKey] else { return } guard let tabIndexAny = notification.userInfo?[Ghostty.Notification.GotoTabKey] else { return }
@ -135,8 +137,7 @@ struct PrimaryView: View {
// Just like in `onGotoTab`, we might receive this multiple times. But // Just like in `onGotoTab`, we might receive this multiple times. But
// it's fine, because `toggleFullscreen` should only apply to the // it's fine, because `toggleFullscreen` should only apply to the
// currently focused window. // currently focused window.
guard let window = self.window else { return } guard self.window.isKeyWindow else { return }
guard window.isKeyWindow else { return }
// Check whether we use non-native fullscreen // Check whether we use non-native fullscreen
guard let useNonNativeFullscreenAny = notification.userInfo?[Ghostty.Notification.NonNativeFullscreenKey] else { return } guard let useNonNativeFullscreenAny = notification.userInfo?[Ghostty.Notification.NonNativeFullscreenKey] else { return }

View File

@ -41,7 +41,8 @@ class PrimaryWindow: NSWindow {
ghostty: ghostty, ghostty: ghostty,
appDelegate: appDelegate, appDelegate: appDelegate,
focusedSurfaceWrapper: window.focusedSurfaceWrapper, focusedSurfaceWrapper: window.focusedSurfaceWrapper,
baseConfig: baseConfig baseConfig: baseConfig,
window: window
)) ))
// We do want to cascade when new windows are created // We do want to cascade when new windows are created

View File

@ -143,7 +143,8 @@ extension Ghostty {
goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) }, goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) },
toggle_fullscreen_cb: { userdata, nonNativeFullscreen in AppState.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) }, toggle_fullscreen_cb: { userdata, nonNativeFullscreen in AppState.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) },
set_initial_window_size_cb: { userdata, width, height in AppState.setInitialWindowSize(userdata, width: width, height: height) }, set_initial_window_size_cb: { userdata, width, height in AppState.setInitialWindowSize(userdata, width: width, height: height) },
render_inspector_cb: { userdata in AppState.renderInspector(userdata) } render_inspector_cb: { userdata in AppState.renderInspector(userdata) },
set_cell_size_cb: { userdata, width, height in AppState.setCellSize(userdata, width: width, height: height) }
) )
// Create the ghostty app. // Create the ghostty app.
@ -468,6 +469,12 @@ extension Ghostty {
surfaceView.initialSize = NSMakeSize(Double(width), Double(height)) surfaceView.initialSize = NSMakeSize(Double(width), Double(height))
} }
static func setCellSize(_ userdata: UnsafeMutableRawPointer?, width: UInt32, height: UInt32) {
guard let surfaceView = self.surfaceUserdata(from: userdata) else { return }
let backingSize = NSSize(width: Double(width), height: Double(height))
surfaceView.cellSize = surfaceView.convertFromBacking(backingSize)
}
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) { static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }

View File

@ -78,6 +78,7 @@ extension Ghostty {
.focused($surfaceFocus) .focused($surfaceFocus)
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title) .focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
.focusedValue(\.ghosttySurfaceView, surfaceView) .focusedValue(\.ghosttySurfaceView, surfaceView)
.focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize)
.onReceive(pubBecomeKey) { notification in .onReceive(pubBecomeKey) { notification in
guard let window = notification.object as? NSWindow else { return } guard let window = notification.object as? NSWindow else { return }
guard let surfaceWindow = surfaceView.window else { return } guard let surfaceWindow = surfaceView.window else { return }
@ -240,7 +241,9 @@ extension Ghostty {
// changed with escape codes. This is public because the callbacks go // changed with escape codes. This is public because the callbacks go
// to the app level and it is set from there. // to the app level and it is set from there.
@Published var title: String = "👻" @Published var title: String = "👻"
@Published var cellSize: NSSize = .init()
// An initial size to request for a window. This will only affect // An initial size to request for a window. This will only affect
// then the view is moved to a new window. // then the view is moved to a new window.
var initialSize: NSSize? = nil var initialSize: NSSize? = nil
@ -930,3 +933,14 @@ extension FocusedValues {
typealias Value = Bool typealias Value = Bool
} }
} }
extension FocusedValues {
var ghosttySurfaceCellSize: NSSize? {
get { self[FocusedGhosttySurfaceCellSize.self] }
set { self[FocusedGhosttySurfaceCellSize.self] = newValue }
}
struct FocusedGhosttySurfaceCellSize: FocusedValueKey {
typealias Value = NSSize
}
}

View File

@ -1,16 +0,0 @@
import SwiftUI
/// Allows accessing the window that this view is a part of.
struct WindowAccessor: NSViewRepresentable {
@Binding var window: NSWindow?
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
self.window = view.window
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
}

View File

@ -460,6 +460,9 @@ pub fn init(
.config = try DerivedConfig.init(alloc, config), .config = try DerivedConfig.init(alloc, config),
}; };
// Report initial cell size on surface creation
try rt_surface.setCellSize(cell_size.width, cell_size.height);
// Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app // Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app
// but is otherwise somewhat arbitrary. // but is otherwise somewhat arbitrary.
try rt_surface.setSizeLimits(.{ try rt_surface.setSizeLimits(.{
@ -896,6 +899,9 @@ fn setCellSize(self: *Surface, size: renderer.CellSize) !void {
}, },
}, .{ .forever = {} }); }, .{ .forever = {} });
self.io_thread.wakeup.notify() catch {}; self.io_thread.wakeup.notify() catch {};
// Notify the window
try self.rt_surface.setCellSize(size.width, size.height);
} }
/// Change the font size. /// Change the font size.

View File

@ -98,6 +98,9 @@ pub const App = struct {
/// Render the inspector for the given surface. /// Render the inspector for the given surface.
render_inspector: ?*const fn (SurfaceUD) callconv(.C) void = null, render_inspector: ?*const fn (SurfaceUD) callconv(.C) void = null,
/// Called when the cell size changes.
set_cell_size: ?*const fn (SurfaceUD, u32, u32) callconv(.C) void = null,
}; };
/// Special values for the goto_tab callback. /// Special values for the goto_tab callback.
@ -818,6 +821,15 @@ pub const Surface = struct {
func(self.opts.userdata); func(self.opts.userdata);
} }
pub fn setCellSize(self: *const Surface, width: u32, height: u32) !void {
const func = self.app.opts.set_cell_size orelse {
log.info("runtime embedder does not support set_cell_size", .{});
return;
};
func(self.opts.userdata, width, height);
}
fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options { fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options {
const font_size: u16 = font_size: { const font_size: u16 = font_size: {
if (!self.app.config.@"window-inherit-font-size") break :font_size 0; if (!self.app.config.@"window-inherit-font-size") break :font_size 0;

View File

@ -461,6 +461,13 @@ pub const Surface = struct {
self.window.setSize(.{ .width = width, .height = height }); self.window.setSize(.{ .width = width, .height = height });
} }
/// Set the cell size. Unused by GLFW.
pub fn setCellSize(self: *const Surface, width: u32, height: u32) !void {
_ = self;
_ = width;
_ = height;
}
/// Set the size limits of the window. /// Set the size limits of the window.
/// Note: this interface is not good, we should redo it if we plan /// Note: this interface is not good, we should redo it if we plan
/// to use this more. i.e. you can't set max width but no max height, /// to use this more. i.e. you can't set max width but no max height,

View File

@ -374,6 +374,12 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void
); );
} }
pub fn setCellSize(self: *const Surface, width: u32, height: u32) !void {
_ = self;
_ = width;
_ = height;
}
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void { pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
_ = self; _ = self;
_ = min; _ = min;