mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
Merge 397ca1feaeaf6a6e66ee116df7d0aa6aadacc580 into 015efcf9e562ac89da49d50ba8b2b2612ef13d2a
This commit is contained in:
@ -450,6 +450,23 @@ typedef struct {
|
||||
ghostty_config_color_s colors[256];
|
||||
} ghostty_config_palette_s;
|
||||
|
||||
// config.QuickTerminalSize
|
||||
typedef enum {
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_NONE,
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_PERCENTAGE,
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_PIXELS,
|
||||
} ghostty_quick_terminal_size_e;
|
||||
|
||||
typedef struct {
|
||||
ghostty_quick_terminal_size_e type;
|
||||
uint32_t value;
|
||||
} ghostty_quick_terminal_size_s;
|
||||
|
||||
typedef struct {
|
||||
ghostty_quick_terminal_size_s primary;
|
||||
ghostty_quick_terminal_size_s secondary;
|
||||
} ghostty_config_quick_terminal_size_s;
|
||||
|
||||
// apprt.Target.Key
|
||||
typedef enum {
|
||||
GHOSTTY_TARGET_APP,
|
||||
|
@ -104,6 +104,7 @@
|
||||
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */; };
|
||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||
A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */; };
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */; };
|
||||
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
|
||||
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; };
|
||||
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; };
|
||||
@ -252,6 +253,7 @@
|
||||
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
||||
A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMenuItem+Extension.swift"; sourceTree = "<group>"; };
|
||||
A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalSize.swift; sourceTree = "<group>"; };
|
||||
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = "<group>"; };
|
||||
A5CA378D2D31D6C100931030 /* Weak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
|
||||
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = "<group>"; };
|
||||
@ -488,6 +490,7 @@
|
||||
A55B7BB429B6F4410055DE60 /* Ghostty */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */,
|
||||
A55B7BB729B6F53A0055DE60 /* Package.swift */,
|
||||
A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */,
|
||||
A5333E212B5A2128008AEFF7 /* SurfaceView_AppKit.swift */,
|
||||
@ -939,6 +942,10 @@
|
||||
A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */,
|
||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */,
|
||||
A57D79272C9C879B001D522E /* SecureInput.swift in Sources */,
|
||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||
A5593FE12DF8D74000B47B10 /* HiddenTitlebarTerminalWindow.swift in Sources */,
|
||||
|
@ -22,7 +22,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
private var previousActiveSpace: CGSSpace? = nil
|
||||
|
||||
/// The window frame saved when the quick terminal's surface tree becomes empty.
|
||||
///
|
||||
///
|
||||
/// This preserves the user's window size and position when all terminal surfaces
|
||||
/// are closed (e.g., via the `exit` command). When a new surface is created,
|
||||
/// the window will be restored to this frame, preventing SwiftUI from resetting
|
||||
@ -34,6 +34,9 @@ class QuickTerminalController: BaseTerminalController {
|
||||
|
||||
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
||||
private var derivedConfig: DerivedConfig
|
||||
|
||||
/// Tracks if we're currently handling a manual resize to prevent recursion
|
||||
private var isHandlingResize: Bool = false
|
||||
|
||||
init(_ ghostty: Ghostty.App,
|
||||
position: QuickTerminalPosition = .top,
|
||||
@ -76,6 +79,11 @@ class QuickTerminalController: BaseTerminalController {
|
||||
selector: #selector(onNewTab),
|
||||
name: Ghostty.Notification.ghosttyNewTab,
|
||||
object: nil)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(windowDidResize(_:)),
|
||||
name: NSWindow.didResizeNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -109,7 +117,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
syncAppearance()
|
||||
|
||||
// Setup our initial size based on our configured position
|
||||
position.setLoaded(window)
|
||||
position.setLoaded(window, size: derivedConfig.quickTerminalSize)
|
||||
|
||||
// Setup our content
|
||||
window.contentView = NSHostingView(rootView: TerminalView(
|
||||
@ -195,10 +203,45 @@ class QuickTerminalController: BaseTerminalController {
|
||||
}
|
||||
|
||||
func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize {
|
||||
// We use the actual screen the window is on for this, since it should
|
||||
// be on the proper screen.
|
||||
guard let screen = window?.screen ?? NSScreen.main else { return frameSize }
|
||||
return position.restrictFrameSize(frameSize, on: screen)
|
||||
// Allow unrestricted resizing - users have full control
|
||||
return frameSize
|
||||
}
|
||||
|
||||
override func windowDidResize(_ notification: Notification) {
|
||||
guard let window = notification.object as? NSWindow,
|
||||
window == self.window,
|
||||
visible,
|
||||
!isHandlingResize else { return }
|
||||
|
||||
// For centered positions (top, bottom, center), we need to recenter the window
|
||||
// when it's manually resized to maintain proper positioning
|
||||
switch position {
|
||||
case .top, .bottom, .center:
|
||||
recenterWindow(window)
|
||||
case .left, .right:
|
||||
// For side positions, we may need to adjust vertical centering
|
||||
recenterWindowVertically(window)
|
||||
}
|
||||
}
|
||||
|
||||
private func recenterWindow(_ window: NSWindow) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
|
||||
isHandlingResize = true
|
||||
defer { isHandlingResize = false }
|
||||
|
||||
let newOrigin = position.centeredOrigin(for: window, on: screen)
|
||||
window.setFrameOrigin(newOrigin)
|
||||
}
|
||||
|
||||
private func recenterWindowVertically(_ window: NSWindow) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
|
||||
isHandlingResize = true
|
||||
defer { isHandlingResize = false }
|
||||
|
||||
let newOrigin = position.verticallyCenteredOrigin(for: window, on: screen)
|
||||
window.setFrameOrigin(newOrigin)
|
||||
}
|
||||
|
||||
// MARK: Base Controller Overrides
|
||||
@ -320,13 +363,15 @@ class QuickTerminalController: BaseTerminalController {
|
||||
guard let screen = derivedConfig.quickTerminalScreen.screen else { return }
|
||||
|
||||
// Restore our previous frame if we have one
|
||||
var preserveSize: NSSize? = nil
|
||||
if let lastClosedFrame {
|
||||
window.setFrame(lastClosedFrame, display: false)
|
||||
preserveSize = lastClosedFrame.size
|
||||
self.lastClosedFrame = nil
|
||||
}
|
||||
|
||||
// Move our window off screen to the top
|
||||
position.setInitial(in: window, on: screen)
|
||||
position.setInitial(in: window, on: screen, terminalSize: derivedConfig.quickTerminalSize, preserveSize: preserveSize)
|
||||
|
||||
// We need to set our window level to a high value. In testing, only
|
||||
// popUpMenu and above do what we want. This gets it above the menu bar
|
||||
@ -357,7 +402,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
context.duration = derivedConfig.quickTerminalAnimationDuration
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
position.setFinal(in: window.animator(), on: screen)
|
||||
position.setFinal(in: window.animator(), on: screen, terminalSize: derivedConfig.quickTerminalSize, preserveSize: preserveSize)
|
||||
}, completionHandler: {
|
||||
// There is a very minor delay here so waiting at least an event loop tick
|
||||
// keeps us safe from the view not being on the window.
|
||||
@ -481,7 +526,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
context.duration = derivedConfig.quickTerminalAnimationDuration
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
position.setInitial(in: window.animator(), on: screen)
|
||||
position.setInitial(in: window.animator(), on: screen, terminalSize: derivedConfig.quickTerminalSize, preserveSize: window.frame.size)
|
||||
}, completionHandler: {
|
||||
// This causes the window to be removed from the screen list and macOS
|
||||
// handles what should be focused next.
|
||||
@ -612,6 +657,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
let quickTerminalAnimationDuration: Double
|
||||
let quickTerminalAutoHide: Bool
|
||||
let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior
|
||||
let quickTerminalSize: QuickTerminalSize
|
||||
let backgroundOpacity: Double
|
||||
|
||||
init() {
|
||||
@ -619,6 +665,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
self.quickTerminalAnimationDuration = 0.2
|
||||
self.quickTerminalAutoHide = true
|
||||
self.quickTerminalSpaceBehavior = .move
|
||||
self.quickTerminalSize = QuickTerminalSize()
|
||||
self.backgroundOpacity = 1.0
|
||||
}
|
||||
|
||||
@ -627,6 +674,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
|
||||
self.quickTerminalAutoHide = config.quickTerminalAutoHide
|
||||
self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior
|
||||
self.quickTerminalSize = config.quickTerminalSize
|
||||
self.backgroundOpacity = config.backgroundOpacity
|
||||
}
|
||||
}
|
||||
|
@ -8,94 +8,69 @@ enum QuickTerminalPosition : String {
|
||||
case center
|
||||
|
||||
/// Set the loaded state for a window.
|
||||
func setLoaded(_ window: NSWindow) {
|
||||
func setLoaded(_ window: NSWindow, size: QuickTerminalSize) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width,
|
||||
height: screen.frame.height / 4)
|
||||
), display: false)
|
||||
|
||||
case .left, .right:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 4,
|
||||
height: screen.frame.height)
|
||||
), display: false)
|
||||
|
||||
case .center:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 2,
|
||||
height: screen.frame.height / 3)
|
||||
), display: false)
|
||||
}
|
||||
let dimensions = size.calculate(position: self, screenDimensions: screen.frame.size)
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: dimensions.width,
|
||||
height: dimensions.height)
|
||||
), display: false)
|
||||
}
|
||||
|
||||
/// Set the initial state for a window for animating out of this position.
|
||||
func setInitial(in window: NSWindow, on screen: NSScreen) {
|
||||
func setInitial(in window: NSWindow, on screen: NSScreen, terminalSize: QuickTerminalSize, preserveSize: NSSize? = nil) {
|
||||
// We always start invisible
|
||||
window.alphaValue = 0
|
||||
|
||||
// Position depends
|
||||
window.setFrame(.init(
|
||||
origin: initialOrigin(for: window, on: screen),
|
||||
size: restrictFrameSize(window.frame.size, on: screen)
|
||||
size: configuredFrameSize(on: screen, terminalSize: terminalSize, preserveExisting: preserveSize)
|
||||
), display: false)
|
||||
}
|
||||
|
||||
/// Set the final state for a window in this position.
|
||||
func setFinal(in window: NSWindow, on screen: NSScreen) {
|
||||
func setFinal(in window: NSWindow, on screen: NSScreen, terminalSize: QuickTerminalSize, preserveSize: NSSize? = nil) {
|
||||
// We always end visible
|
||||
window.alphaValue = 1
|
||||
|
||||
// Position depends
|
||||
window.setFrame(.init(
|
||||
origin: finalOrigin(for: window, on: screen),
|
||||
size: restrictFrameSize(window.frame.size, on: screen)
|
||||
size: configuredFrameSize(on: screen, terminalSize: terminalSize, preserveExisting: preserveSize)
|
||||
), display: true)
|
||||
}
|
||||
|
||||
/// Restrict the frame size during resizing.
|
||||
func restrictFrameSize(_ size: NSSize, on screen: NSScreen) -> NSSize {
|
||||
var finalSize = size
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
finalSize.width = screen.frame.width
|
||||
|
||||
case .left, .right:
|
||||
finalSize.height = screen.visibleFrame.height
|
||||
|
||||
case .center:
|
||||
finalSize.width = screen.frame.width / 2
|
||||
finalSize.height = screen.frame.height / 3
|
||||
/// Get the configured frame size for initial positioning and animations.
|
||||
func configuredFrameSize(on screen: NSScreen, terminalSize: QuickTerminalSize, preserveExisting: NSSize? = nil) -> NSSize {
|
||||
// If we have existing dimensions from manual resizing, preserve them
|
||||
if let existing = preserveExisting, existing.width > 0 && existing.height > 0 {
|
||||
return existing
|
||||
}
|
||||
|
||||
return finalSize
|
||||
|
||||
let dimensions = terminalSize.calculate(position: self, screenDimensions: screen.frame.size)
|
||||
return NSSize(width: dimensions.width, height: dimensions.height)
|
||||
}
|
||||
|
||||
/// The initial point origin for this position.
|
||||
func initialOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch (self) {
|
||||
case .top:
|
||||
return .init(x: screen.frame.minX, y: screen.frame.maxY)
|
||||
return .init(x: round(screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2), y: screen.frame.maxY)
|
||||
|
||||
case .bottom:
|
||||
return .init(x: screen.frame.minX, y: -window.frame.height)
|
||||
return .init(x: round(screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2), y: -window.frame.height)
|
||||
|
||||
case .left:
|
||||
return .init(x: screen.frame.minX-window.frame.width, y: 0)
|
||||
return .init(x: screen.frame.minX-window.frame.width, y: round(screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2))
|
||||
|
||||
case .right:
|
||||
return .init(x: screen.frame.maxX, y: 0)
|
||||
return .init(x: screen.frame.maxX, y: round(screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2))
|
||||
|
||||
case .center:
|
||||
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.height - window.frame.width)
|
||||
return .init(x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2), y: screen.visibleFrame.height - window.frame.width)
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,19 +78,19 @@ enum QuickTerminalPosition : String {
|
||||
func finalOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch (self) {
|
||||
case .top:
|
||||
return .init(x: screen.frame.minX, y: screen.visibleFrame.maxY - window.frame.height)
|
||||
return .init(x: round(screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2), y: screen.visibleFrame.maxY - window.frame.height)
|
||||
|
||||
case .bottom:
|
||||
return .init(x: screen.frame.minX, y: screen.frame.minY)
|
||||
return .init(x: round(screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2), y: screen.frame.minY)
|
||||
|
||||
case .left:
|
||||
return .init(x: screen.frame.minX, y: window.frame.origin.y)
|
||||
return .init(x: screen.frame.minX, y: round(screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2))
|
||||
|
||||
case .right:
|
||||
return .init(x: screen.visibleFrame.maxX - window.frame.width, y: window.frame.origin.y)
|
||||
return .init(x: screen.visibleFrame.maxX - window.frame.width, y: round(screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2))
|
||||
|
||||
case .center:
|
||||
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
return .init(x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2), y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,4 +111,52 @@ enum QuickTerminalPosition : String {
|
||||
case .right: self == .top || self == .bottom
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the centered origin for a window, keeping it properly positioned after manual resizing
|
||||
func centeredOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch self {
|
||||
case .top:
|
||||
return CGPoint(
|
||||
x: round(screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2),
|
||||
y: window.frame.origin.y // Keep the same Y position
|
||||
)
|
||||
|
||||
case .bottom:
|
||||
return CGPoint(
|
||||
x: round(screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2),
|
||||
y: window.frame.origin.y // Keep the same Y position
|
||||
)
|
||||
|
||||
case .center:
|
||||
return CGPoint(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .left, .right:
|
||||
// For left/right positions, only adjust horizontal centering if needed
|
||||
return window.frame.origin
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the vertically centered origin for side-positioned windows
|
||||
func verticallyCenteredOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch self {
|
||||
case .left:
|
||||
return CGPoint(
|
||||
x: window.frame.origin.x, // Keep the same X position
|
||||
y: round(screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .right:
|
||||
return CGPoint(
|
||||
x: window.frame.origin.x, // Keep the same X position
|
||||
y: round(screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .top, .bottom, .center:
|
||||
// These positions don't need vertical recentering during resize
|
||||
return window.frame.origin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -475,6 +475,14 @@ extension Ghostty {
|
||||
let str = String(cString: ptr)
|
||||
return QuickTerminalSpaceBehavior(fromGhosttyConfig: str) ?? .move
|
||||
}
|
||||
|
||||
var quickTerminalSize: QuickTerminalSize {
|
||||
guard let config = self.config else { return QuickTerminalSize() }
|
||||
var v = ghostty_config_quick_terminal_size_s()
|
||||
let key = "quick-terminal-size"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return QuickTerminalSize() }
|
||||
return QuickTerminalSize(from: v)
|
||||
}
|
||||
#endif
|
||||
|
||||
var resizeOverlay: ResizeOverlay {
|
||||
|
84
macos/Sources/Ghostty/QuickTerminalSize.swift
Normal file
84
macos/Sources/Ghostty/QuickTerminalSize.swift
Normal file
@ -0,0 +1,84 @@
|
||||
import GhosttyKit
|
||||
|
||||
struct QuickTerminalSize {
|
||||
let primary: Size?
|
||||
let secondary: Size?
|
||||
|
||||
init(primary: Size? = nil, secondary: Size? = nil) {
|
||||
self.primary = primary
|
||||
self.secondary = secondary
|
||||
}
|
||||
|
||||
init(from cStruct: ghostty_config_quick_terminal_size_s) {
|
||||
self.primary = Size(from: cStruct.primary)
|
||||
self.secondary = Size(from: cStruct.secondary)
|
||||
}
|
||||
|
||||
enum Size {
|
||||
case percentage(Float)
|
||||
case pixels(UInt32)
|
||||
|
||||
init?(from cStruct: ghostty_quick_terminal_size_s) {
|
||||
switch cStruct.type {
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_NONE:
|
||||
return nil
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_PERCENTAGE:
|
||||
let floatValue = withUnsafePointer(to: cStruct.value) { ptr in
|
||||
ptr.withMemoryRebound(to: Float.self, capacity: 1) { $0.pointee }
|
||||
}
|
||||
self = .percentage(floatValue)
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_PIXELS:
|
||||
self = .pixels(cStruct.value)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func toPixels(parentDimension: CGFloat) -> CGFloat {
|
||||
switch self {
|
||||
case .percentage(let value):
|
||||
return parentDimension * CGFloat(value) / 100.0
|
||||
case .pixels(let value):
|
||||
return CGFloat(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Dimensions {
|
||||
let width: CGFloat
|
||||
let height: CGFloat
|
||||
}
|
||||
|
||||
func calculate(position: QuickTerminalPosition, screenDimensions: CGSize) -> Dimensions {
|
||||
let dims = Dimensions(width: screenDimensions.width, height: screenDimensions.height)
|
||||
|
||||
switch position {
|
||||
case .left, .right:
|
||||
return Dimensions(
|
||||
width: primary?.toPixels(parentDimension: dims.width) ?? 400,
|
||||
height: secondary?.toPixels(parentDimension: dims.height) ?? dims.height
|
||||
)
|
||||
|
||||
case .top, .bottom:
|
||||
return Dimensions(
|
||||
width: secondary?.toPixels(parentDimension: dims.width) ?? dims.width,
|
||||
height: primary?.toPixels(parentDimension: dims.height) ?? 400
|
||||
)
|
||||
|
||||
case .center:
|
||||
if dims.width >= dims.height {
|
||||
// Landscape
|
||||
return Dimensions(
|
||||
width: primary?.toPixels(parentDimension: dims.width) ?? 800,
|
||||
height: secondary?.toPixels(parentDimension: dims.height) ?? 400
|
||||
)
|
||||
} else {
|
||||
// Portrait
|
||||
return Dimensions(
|
||||
width: secondary?.toPixels(parentDimension: dims.width) ?? 400,
|
||||
height: primary?.toPixels(parentDimension: dims.height) ?? 800
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7063,6 +7063,42 @@ pub const QuickTerminalSize = struct {
|
||||
height: u32,
|
||||
};
|
||||
|
||||
/// C API structure for QuickTerminalSize
|
||||
pub const C = extern struct {
|
||||
primary: CSize,
|
||||
secondary: CSize,
|
||||
};
|
||||
|
||||
pub const CSize = extern struct {
|
||||
type: Type,
|
||||
value: u32,
|
||||
|
||||
pub const Type = enum(u8) { none, percentage, pixels };
|
||||
|
||||
pub const none: CSize = .{ .type = .none, .value = 0 };
|
||||
|
||||
fn percentage(v: f32) CSize {
|
||||
return .{ .type = .percentage, .value = @bitCast(v) };
|
||||
}
|
||||
|
||||
fn pixels(v: u32) CSize {
|
||||
return .{ .type = .pixels, .value = v };
|
||||
}
|
||||
};
|
||||
|
||||
pub fn cval(self: QuickTerminalSize) C {
|
||||
return .{
|
||||
.primary = if (self.primary) |p| switch (p) {
|
||||
.percentage => |v| .percentage(v),
|
||||
.pixels => |v| .pixels(v),
|
||||
} else .none,
|
||||
.secondary = if (self.secondary) |s| switch (s) {
|
||||
.percentage => |v| .percentage(v),
|
||||
.pixels => |v| .pixels(v),
|
||||
} else .none,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn calculate(
|
||||
self: QuickTerminalSize,
|
||||
position: QuickTerminalPosition,
|
||||
|
Reference in New Issue
Block a user