mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
"Return to Default Size" implementation (#5974)
## Added Support for "Return To Default Size" This update introduces support for the **"Return To Default Size"** feature. ### Fixes - Resolves [#1328](https://github.com/ghostty-org/ghostty/issues/1328) ### Screenshots | Description | Screenshot | |------------|------------| | **Ghostty** | <img width="1084" alt="Screenshot 2025-02-24 at 21 15 38" src="https://github.com/user-attachments/assets/4657ccdb-9c7a-4884-873c-bbe0f30f9400" /> | | **After changing window size** | <img width="1155" alt="Screenshot 2025-02-24 at 21 16 00" src="https://github.com/user-attachments/assets/9b3931f2-1c4b-4f86-8d56-8892bd5675cc" /> | | **Native Terminal App (for reference)** | <img width="630" alt="Screenshot 2025-02-24 at 21 16 20" src="https://github.com/user-attachments/assets/ae049931-b74d-4246-a9e7-d9be079b1a24" /> |
This commit is contained in:
@ -388,6 +388,13 @@
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="dgt-Tx-d4e"/>
|
||||
<menuItem title="Return To Default Size" id="Gbx-Vi-OGC" userLabel="Return To Default Size">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="returnToDefaultSize:" target="-1" id="Bpt-GO-UU1"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="CpM-rI-Sc1"/>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
|
@ -27,6 +27,9 @@ class TerminalController: BaseTerminalController {
|
||||
/// The notification cancellable for focused surface property changes.
|
||||
private var surfaceAppearanceCancellables: Set<AnyCancellable> = []
|
||||
|
||||
/// This will be set to the initial frame of the window from the xib on load.
|
||||
private var initialFrame: NSRect? = nil
|
||||
|
||||
init(_ ghostty: Ghostty.App,
|
||||
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
||||
withSurfaceTree tree: Ghostty.SplitNode? = nil
|
||||
@ -308,6 +311,55 @@ class TerminalController: BaseTerminalController {
|
||||
y: frame.maxY - (CGFloat(y) + window.frame.height)))
|
||||
}
|
||||
|
||||
/// Returns the default size of the window. This is contextual based on the focused surface because
|
||||
/// the focused surface may specify a different default size than others.
|
||||
private var defaultSize: NSRect? {
|
||||
guard let screen = window?.screen ?? NSScreen.main else { return nil }
|
||||
|
||||
if derivedConfig.maximize {
|
||||
return screen.visibleFrame
|
||||
} else if let focusedSurface,
|
||||
let initialSize = focusedSurface.initialSize {
|
||||
// Get the current frame of the window
|
||||
guard var frame = window?.frame else { return nil }
|
||||
|
||||
// Calculate the chrome size (window size minus view size)
|
||||
let chromeWidth = frame.size.width - focusedSurface.frame.size.width
|
||||
let chromeHeight = frame.size.height - focusedSurface.frame.size.height
|
||||
|
||||
// Calculate the new width and height, clamping to the screen's size
|
||||
let newWidth = min(initialSize.width + chromeWidth, screen.visibleFrame.width)
|
||||
let newHeight = min(initialSize.height + chromeHeight, screen.visibleFrame.height)
|
||||
|
||||
// Update the frame size while keeping the window's position intact
|
||||
frame.size.width = newWidth
|
||||
frame.size.height = newHeight
|
||||
|
||||
// Ensure the window doesn't go outside the screen boundaries
|
||||
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
|
||||
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
guard let initialFrame else { return nil }
|
||||
guard var frame = window?.frame else { return nil }
|
||||
|
||||
// Calculate the new width and height, clamping to the screen's size
|
||||
let newWidth = min(initialFrame.size.width, screen.visibleFrame.width)
|
||||
let newHeight = min(initialFrame.size.height, screen.visibleFrame.height)
|
||||
|
||||
// Update the frame size while keeping the window's position intact
|
||||
frame.size.width = newWidth
|
||||
frame.size.height = newHeight
|
||||
|
||||
// Ensure the window doesn't go outside the screen boundaries
|
||||
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
|
||||
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
//MARK: - NSWindowController
|
||||
|
||||
override func windowWillLoad() {
|
||||
@ -356,6 +408,9 @@ class TerminalController: BaseTerminalController {
|
||||
super.windowDidLoad()
|
||||
guard let window = window as? TerminalWindow else { return }
|
||||
|
||||
// Store our initial frame so we can know our default later.
|
||||
initialFrame = window.frame
|
||||
|
||||
// I copy this because we may change the source in the future but also because
|
||||
// I regularly audit our codebase for "ghostty.config" access because generally
|
||||
// you shouldn't use it. Its safe in this case because for a new window we should
|
||||
@ -372,36 +427,15 @@ class TerminalController: BaseTerminalController {
|
||||
// If window decorations are disabled, remove our title
|
||||
if (!config.windowDecorations) { window.styleMask.remove(.titled) }
|
||||
|
||||
// If we have only a single surface (no splits) and that surface requested
|
||||
// an initial size then we set it here now.
|
||||
// If we have only a single surface (no splits) and there is a default size then
|
||||
// we should resize to that default size.
|
||||
if case let .leaf(leaf) = surfaceTree {
|
||||
if config.maximize {
|
||||
if let screen = window.screen ?? NSScreen.main {
|
||||
window.setFrame(screen.visibleFrame, display: true)
|
||||
}
|
||||
} else if let initialSize = leaf.surface.initialSize,
|
||||
let screen = window.screen ?? NSScreen.main {
|
||||
// Get the current frame of the window
|
||||
var frame = window.frame
|
||||
// If this is our first surface then our focused surface will be nil
|
||||
// so we force the focused surface to the leaf.
|
||||
focusedSurface = leaf.surface
|
||||
|
||||
// Calculate the chrome size (window size minus view size)
|
||||
let chromeWidth = frame.size.width - leaf.surface.frame.size.width
|
||||
let chromeHeight = frame.size.height - leaf.surface.frame.size.height
|
||||
|
||||
// Calculate the new width and height, clamping to the screen's size
|
||||
let newWidth = min(initialSize.width + chromeWidth, screen.visibleFrame.width)
|
||||
let newHeight = min(initialSize.height + chromeHeight, screen.visibleFrame.height)
|
||||
|
||||
// Update the frame size while keeping the window's position intact
|
||||
frame.size.width = newWidth
|
||||
frame.size.height = newHeight
|
||||
|
||||
// Ensure the window doesn't go outside the screen boundaries
|
||||
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
|
||||
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
|
||||
|
||||
// Set the updated frame to the window
|
||||
window.setFrame(frame, display: true)
|
||||
if let defaultSize {
|
||||
window.setFrame(defaultSize, display: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,6 +612,11 @@ class TerminalController: BaseTerminalController {
|
||||
window.close()
|
||||
}
|
||||
|
||||
@IBAction func returnToDefaultSize(_ sender: Any) {
|
||||
guard let defaultSize else { return }
|
||||
window?.setFrame(defaultSize, display: true)
|
||||
}
|
||||
|
||||
@IBAction override func closeWindow(_ sender: Any?) {
|
||||
guard let window = window else { return }
|
||||
guard let tabGroup = window.tabGroup else {
|
||||
@ -811,15 +850,55 @@ class TerminalController: BaseTerminalController {
|
||||
struct DerivedConfig {
|
||||
let backgroundColor: Color
|
||||
let macosTitlebarStyle: String
|
||||
let maximize: Bool
|
||||
|
||||
init() {
|
||||
self.backgroundColor = Color(NSColor.windowBackgroundColor)
|
||||
self.macosTitlebarStyle = "system"
|
||||
self.maximize = false
|
||||
}
|
||||
|
||||
init(_ config: Ghostty.Config) {
|
||||
self.backgroundColor = config.backgroundColor
|
||||
self.macosTitlebarStyle = config.macosTitlebarStyle
|
||||
self.maximize = config.maximize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension TerminalController: NSMenuItemValidation {
|
||||
func validateMenuItem(_ item: NSMenuItem) -> Bool {
|
||||
switch item.action {
|
||||
case #selector(returnToDefaultSize):
|
||||
guard let window else { return false }
|
||||
|
||||
// Native fullscreen windows can't revert to default size.
|
||||
if window.styleMask.contains(.fullScreen) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we're fullscreen at all then we can't change size
|
||||
if fullscreenStyle?.isFullscreen ?? false {
|
||||
return false
|
||||
}
|
||||
|
||||
// If our window is already the default size or we don't have a
|
||||
// default size, then disable.
|
||||
guard let defaultSize,
|
||||
window.frame.size != .init(
|
||||
width: defaultSize.size.width,
|
||||
height: defaultSize.size.height
|
||||
)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ extension Ghostty {
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
|
||||
var windowPositionX: Int16? {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: Int16 = 0
|
||||
|
Reference in New Issue
Block a user