Merge pull request #2341 from ghostty-org/push-noxxuypymuoz

macos: quick terminal supports fullscreen
This commit is contained in:
Mitchell Hashimoto
2024-09-30 21:15:26 -07:00
committed by GitHub
4 changed files with 106 additions and 67 deletions

View File

@ -25,12 +25,26 @@ class QuickTerminalController: BaseTerminalController {
) {
self.position = position
super.init(ghostty, baseConfig: base, surfaceTree: tree)
// Setup our notifications for behaviors
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(onToggleFullscreen),
name: Ghostty.Notification.ghosttyToggleFullscreen,
object: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported for this view")
}
deinit {
// Remove all of our notificationcenter subscriptions
let center = NotificationCenter.default
center.removeObserver(self)
}
// MARK: NSWindowController
override func windowDidLoad() {
@ -199,6 +213,11 @@ class QuickTerminalController: BaseTerminalController {
// We always animate out to whatever screen the window is actually on.
guard let screen = window.screen ?? NSScreen.main else { return }
// If we are in fullscreen, then we exit fullscreen.
if let fullscreenStyle, fullscreenStyle.isFullscreen {
fullscreenStyle.exit()
}
// If we have a previously active application, restore focus to it. We
// do this BEFORE the animation below because when the animation completes
// macOS will bring forward another window.
@ -239,4 +258,19 @@ class QuickTerminalController: BaseTerminalController {
alert.alertStyle = .warning
alert.beginSheetModal(for: window)
}
@IBAction func toggleGhosttyFullScreen(_ sender: Any) {
guard let surface = focusedSurface?.surface else { return }
ghostty.toggleFullscreen(surface: surface)
}
// MARK: Notifications
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return }
// We ignore the requested mode and always use non-native for the quick terminal
toggleFullscreen(mode: .nonNative)
}
}

View File

@ -11,11 +11,15 @@ import GhosttyKit
/// view the TerminalView SwiftUI view must be used and this class is the view model and
/// delegate.
///
/// Special considerations to implement:
///
/// - Fullscreen: you must manually listen for the right notification and implement the
/// callback that calls toggleFullscreen on this base class.
///
/// Notably, things this class does NOT implement (not exhaustive):
///
/// - Tabbing, because there are many ways to get tabbed behavior in macOS and we
/// don't want to be opinionated about it.
/// - Fullscreen
/// - Window restoration or save state
/// - Window visual styles (such as titlebar colors)
///
@ -25,7 +29,8 @@ class BaseTerminalController: NSWindowController,
NSWindowDelegate,
TerminalViewDelegate,
TerminalViewModel,
ClipboardConfirmationViewDelegate
ClipboardConfirmationViewDelegate,
FullscreenDelegate
{
/// The app instance that this terminal view will represent.
let ghostty: Ghostty.App
@ -46,6 +51,9 @@ class BaseTerminalController: NSWindowController,
/// The clipboard confirmation window, if shown.
private var clipboardConfirmation: ClipboardConfirmationController? = nil
/// Fullscreen state management.
private(set) var fullscreenStyle: FullscreenStyle?
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported for this view")
}
@ -123,6 +131,63 @@ class BaseTerminalController: NSWindowController,
func zoomStateDidChange(to: Bool) {}
// MARK: Fullscreen
/// Toggle fullscreen for the given mode.
func toggleFullscreen(mode: FullscreenMode) {
// We need a window to fullscreen
guard let window = self.window else { return }
// If we have a previous fullscreen style initialized, we want to check if
// our mode changed. If it changed and we're in fullscreen, we exit so we can
// toggle it next time. If it changed and we're not in fullscreen we can just
// switch the handler.
var newStyle = mode.style(for: window)
newStyle?.delegate = self
old: if let oldStyle = self.fullscreenStyle {
// If we're not fullscreen, we can nil it out so we get the new style
if !oldStyle.isFullscreen {
self.fullscreenStyle = newStyle
break old
}
assert(oldStyle.isFullscreen)
// We consider our mode changed if the types change (obvious) but
// also if its nil (not obvious) because nil means that the style has
// likely changed but we don't support it.
if newStyle == nil || type(of: newStyle) != type(of: oldStyle) {
// Our mode changed. Exit fullscreen (since we're toggling anyways)
// and then unset the style so that we replace it next time.
oldStyle.exit()
self.fullscreenStyle = nil
// We're done
return
}
// Style is the same.
} else {
// We have no previous style
self.fullscreenStyle = newStyle
}
guard let fullscreenStyle else { return }
if fullscreenStyle.isFullscreen {
fullscreenStyle.exit()
} else {
fullscreenStyle.enter()
}
}
func fullscreenDidChange() {
// For some reason focus can get lost when we change fullscreen. Regardless of
// mode above we just move it back.
if let focusedSurface {
Ghostty.moveFocus(to: focusedSurface)
}
}
// MARK: Clipboard Confirmation
@objc private func onConfirmClipboardRequest(notification: SwiftUI.Notification) {

View File

@ -4,14 +4,9 @@ import SwiftUI
import GhosttyKit
/// A classic, tabbed terminal experience.
class TerminalController: BaseTerminalController,
FullscreenDelegate
{
class TerminalController: BaseTerminalController {
override var windowNibName: NSNib.Name? { "Terminal" }
/// Fullscreen state management.
private(set) var fullscreenStyle: FullscreenStyle?
/// This is set to true when we care about frame changes. This is a small optimization since
/// this controller registers a listener for ALL frame change notifications and this lets us bail
/// early if we don't care.
@ -200,63 +195,6 @@ class TerminalController: BaseTerminalController,
}
}
// MARK: Fullscreen
/// Toggle fullscreen for the given mode.
func toggleFullscreen(mode: FullscreenMode) {
// We need a window to fullscreen
guard let window = self.window else { return }
// If we have a previous fullscreen style initialized, we want to check if
// our mode changed. If it changed and we're in fullscreen, we exit so we can
// toggle it next time. If it changed and we're not in fullscreen we can just
// switch the handler.
var newStyle = mode.style(for: window)
newStyle?.delegate = self
old: if let oldStyle = self.fullscreenStyle {
// If we're not fullscreen, we can nil it out so we get the new style
if !oldStyle.isFullscreen {
self.fullscreenStyle = newStyle
break old
}
assert(oldStyle.isFullscreen)
// We consider our mode changed if the types change (obvious) but
// also if its nil (not obvious) because nil means that the style has
// likely changed but we don't support it.
if newStyle == nil || type(of: newStyle) != type(of: oldStyle) {
// Our mode changed. Exit fullscreen (since we're toggling anyways)
// and then unset the style so that we replace it next time.
oldStyle.exit()
self.fullscreenStyle = nil
// We're done
return
}
// Style is the same.
} else {
// We have no previous style
self.fullscreenStyle = newStyle
}
guard let fullscreenStyle else { return }
if fullscreenStyle.isFullscreen {
fullscreenStyle.exit()
} else {
fullscreenStyle.enter()
}
}
func fullscreenDidChange() {
// For some reason focus can get lost when we change fullscreen. Regardless of
// mode above we just move it back.
if let focusedSurface {
Ghostty.moveFocus(to: focusedSurface)
}
}
//MARK: - NSWindowController
override func windowWillLoad() {
@ -584,7 +522,6 @@ class TerminalController: BaseTerminalController,
targetWindow.makeKeyAndOrderFront(nil)
}
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return }

View File

@ -376,9 +376,12 @@ pub const Action = union(enum) {
///
/// - It is a singleton; only one instance can exist at a time.
/// - It does not support tabs.
/// - It does not support fullscreen.
/// - It will not be restored when the application is restarted
/// (for systems that support window restoration).
/// - It supports fullscreen, but fullscreen will always be a non-native
/// fullscreen (macos-non-native-fullscreen = true). This only applies
/// to the quick terminal window. This is a requirement due to how
/// the quick terminal is rendered.
///
/// See the various configurations for the quick terminal in the
/// configuration file to customize its behavior.