Merge pull request #735 from gpanders/resize-increments

macos: set window resizeIncrements when cell size changes
This commit is contained in:
Mitchell Hashimoto
2023-10-27 08:09:00 -07:00
committed by GitHub
12 changed files with 89 additions and 33 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_set_initial_window_size_cb)(void *, uint32_t, uint32_t);
typedef void (*ghostty_runtime_render_inspector_cb)(void *);
typedef void (*ghostty_runtime_set_cell_size_cb)(void *, uint32_t, uint32_t);
typedef struct {
void *userdata;
@ -359,6 +360,7 @@ typedef struct {
ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb;
ghostty_runtime_set_initial_window_size_cb set_initial_window_size_cb;
ghostty_runtime_render_inspector_cb render_inspector_cb;
ghostty_runtime_set_cell_size_cb set_cell_size_cb;
} ghostty_runtime_config_s;
//-------------------------------------------------------------------

View File

@ -38,7 +38,6 @@
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.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 */
/* Begin PBXFileReference section */
@ -76,7 +75,6 @@
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>"; };
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 */
/* Begin PBXFrameworksBuildPhase section */
@ -120,7 +118,6 @@
A5CEAFFE29C2410700646FDA /* Backport.swift */,
8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */,
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */,
A5FECBD829D2010400022361 /* WindowAccessor.swift */,
A5CEAFDA29B8005900646FDA /* SplitView */,
);
path = Helpers;
@ -313,7 +310,6 @@
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */,
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
A5FECBD929D2010400022361 /* WindowAccessor.swift in Sources */,
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
85102A1C2A6E32890084AB3E /* PrimaryWindowController.swift in Sources */,
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,

View File

@ -14,9 +14,10 @@ struct PrimaryView: View {
// If this is set, this is the base configuration that we build our surface out of.
let baseConfig: Ghostty.SurfaceConfiguration?
// We need access to our window to know if we're the key window to determine
// if we show the quit confirmation or not.
@State private var window: NSWindow?
// We need access to our window to know if we're the key window and to
// modify window properties in response to events from the surface (e.g.
// updating the window title)
var window: NSWindow
// This handles non-native fullscreen
@State private var fullScreen = FullScreenHandler()
@ -27,6 +28,7 @@ struct PrimaryView: View {
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle
@FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit
@FocusedValue(\.ghosttySurfaceCellSize) private var cellSize
// The title for our window
private var title: String {
@ -68,7 +70,6 @@ struct PrimaryView: View {
Ghostty.TerminalSplit(onClose: Self.closeWindow, baseConfig: self.baseConfig)
.ghosttyApp(ghostty.app!)
.ghosttyConfig(ghostty.config!)
.background(WindowAccessor(window: $window))
.onReceive(gotoTab) { onGotoTab(notification: $0) }
.onReceive(toggleFullscreen) { onToggleFullscreen(notification: $0) }
.focused($focused)
@ -79,8 +80,12 @@ struct PrimaryView: View {
.onChange(of: title) { newValue in
// We need to handle this manually because we are using AppKit lifecycle
// so navigationTitle no longer works.
guard let window = self.window else { return }
window.title = newValue
self.window.title = newValue
}
.onChange(of: cellSize) { newValue in
if !ghostty.windowStepResize { return }
guard let size = newValue else { return }
self.window.contentResizeIncrements = size
}
}
}
@ -95,8 +100,7 @@ struct PrimaryView: View {
// 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
// handle it if we're the focused window.
guard let window = self.window else { return }
guard window.isKeyWindow else { return }
guard self.window.isKeyWindow else { return }
// Get the tab index from the notification
guard let tabIndexAny = notification.userInfo?[Ghostty.Notification.GotoTabKey] else { return }
@ -135,8 +139,7 @@ struct PrimaryView: View {
// Just like in `onGotoTab`, we might receive this multiple times. But
// it's fine, because `toggleFullscreen` should only apply to the
// currently focused window.
guard let window = self.window else { return }
guard window.isKeyWindow else { return }
guard self.window.isKeyWindow else { return }
// Check whether we use non-native fullscreen
guard let useNonNativeFullscreenAny = notification.userInfo?[Ghostty.Notification.NonNativeFullscreenKey] else { return }

View File

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

View File

@ -97,6 +97,15 @@ extension Ghostty {
return String(cString: ptr)
}
/// Whether to resize windows in discrete steps or use "fluid" resizing
var windowStepResize: Bool {
guard let config = self.config else { return true }
var v = false
let key = "window-step-resize"
_ = ghostty_config_get(config, &v, key, UInt(key.count))
return v
}
/// The background opacity.
var backgroundOpacity: Double {
guard let config = self.config else { return 1 }
@ -143,7 +152,8 @@ extension Ghostty {
goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) },
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) },
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.
@ -468,6 +478,12 @@ extension Ghostty {
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) {
guard let surface = self.surfaceUserdata(from: userdata) else { return }

View File

@ -78,6 +78,7 @@ extension Ghostty {
.focused($surfaceFocus)
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
.focusedValue(\.ghosttySurfaceView, surfaceView)
.focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize)
.onReceive(pubBecomeKey) { notification in
guard let window = notification.object as? NSWindow else { return }
guard let surfaceWindow = surfaceView.window else { return }
@ -240,7 +241,13 @@ extension Ghostty {
// changed with escape codes. This is public because the callbacks go
// to the app level and it is set from there.
@Published var title: String = "👻"
// The cell size of this surface. This is set by the core when the
// surface is first created and any time the cell size changes (i.e.
// when the font size changes). This is used to allow windows to be
// resized in discrete steps of a single cell.
@Published var cellSize: NSSize = .zero
// An initial size to request for a window. This will only affect
// then the view is moved to a new window.
var initialSize: NSSize? = nil
@ -930,3 +937,14 @@ extension FocusedValues {
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),
};
// 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
// but is otherwise somewhat arbitrary.
try rt_surface.setSizeLimits(.{
@ -896,6 +899,9 @@ fn setCellSize(self: *Surface, size: renderer.CellSize) !void {
},
}, .{ .forever = {} });
self.io_thread.wakeup.notify() catch {};
// Notify the window
try self.rt_surface.setCellSize(size.width, size.height);
}
/// Change the font size.

View File

@ -98,6 +98,9 @@ pub const App = struct {
/// Render the inspector for the given surface.
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.
@ -818,6 +821,15 @@ pub const Surface = struct {
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 {
const font_size: u16 = font_size: {
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 });
}
/// 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.
/// 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,

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 {
_ = self;
_ = min;

View File

@ -390,6 +390,11 @@ keybind: Keybinds = .{},
@"window-height": u32 = 0,
@"window-width": u32 = 0,
/// Resize the window in discrete increments of the focused surface's
/// cell size. If this is disabled, surfaces are resized in pixel increments.
/// Currently only supported on macOS.
@"window-step-resize": bool = true,
/// Whether to allow programs running in the terminal to read/write to
/// the system clipboard (OSC 52, for googling). The default is to
/// disallow clipboard reading but allow writing.