mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: render a terminal in the slide window
This commit is contained in:
@ -65,6 +65,7 @@
|
||||
A5CBD05C2CA0C5C70017A1AE /* SlideTerminal.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CBD05B2CA0C5C70017A1AE /* SlideTerminal.xib */; };
|
||||
A5CBD05E2CA0C5EC0017A1AE /* SlideTerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD05D2CA0C5E70017A1AE /* SlideTerminalController.swift */; };
|
||||
A5CBD0602CA0C90A0017A1AE /* SlideTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD05F2CA0C9080017A1AE /* SlideTerminalWindow.swift */; };
|
||||
A5CBD0642CA122E70017A1AE /* SlideTerminalPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0632CA122E70017A1AE /* SlideTerminalPosition.swift */; };
|
||||
A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */; };
|
||||
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36142C9CDA03004D6760 /* View+Extension.swift */; };
|
||||
A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */; };
|
||||
@ -139,6 +140,7 @@
|
||||
A5CBD05B2CA0C5C70017A1AE /* SlideTerminal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SlideTerminal.xib; sourceTree = "<group>"; };
|
||||
A5CBD05D2CA0C5E70017A1AE /* SlideTerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideTerminalController.swift; sourceTree = "<group>"; };
|
||||
A5CBD05F2CA0C9080017A1AE /* SlideTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideTerminalWindow.swift; sourceTree = "<group>"; };
|
||||
A5CBD0632CA122E70017A1AE /* SlideTerminalPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideTerminalPosition.swift; sourceTree = "<group>"; };
|
||||
A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInputOverlay.swift; sourceTree = "<group>"; };
|
||||
A5CC36142C9CDA03004D6760 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = "<group>"; };
|
||||
A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationErrors.xib; sourceTree = "<group>"; };
|
||||
@ -393,6 +395,7 @@
|
||||
children = (
|
||||
A5CBD05B2CA0C5C70017A1AE /* SlideTerminal.xib */,
|
||||
A5CBD05D2CA0C5E70017A1AE /* SlideTerminalController.swift */,
|
||||
A5CBD0632CA122E70017A1AE /* SlideTerminalPosition.swift */,
|
||||
A5CBD05F2CA0C9080017A1AE /* SlideTerminalWindow.swift */,
|
||||
);
|
||||
path = SlideTerminal;
|
||||
@ -548,6 +551,7 @@
|
||||
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
|
||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
||||
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */,
|
||||
A5CBD0642CA122E70017A1AE /* SlideTerminalPosition.swift in Sources */,
|
||||
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
||||
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */,
|
||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||
|
@ -170,7 +170,7 @@ class AppDelegate: NSObject,
|
||||
//terminalManager.newWindow()
|
||||
}
|
||||
|
||||
foo = SlideTerminalController(window: nil)
|
||||
foo = SlideTerminalController(ghostty, baseConfig: nil)
|
||||
foo?.showWindow(self)
|
||||
}
|
||||
|
||||
|
@ -4,39 +4,77 @@ import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
/// Controller for the slide-style terminal.
|
||||
class SlideTerminalController: NSWindowController {
|
||||
class SlideTerminalController: NSWindowController, TerminalViewDelegate, TerminalViewModel {
|
||||
override var windowNibName: NSNib.Name? { "SlideTerminal" }
|
||||
|
||||
/// The app instance that this terminal view will represent.
|
||||
let ghostty: Ghostty.App
|
||||
|
||||
/// The position fo the slide terminal.
|
||||
let position: SlideTerminalPosition
|
||||
|
||||
/// The surface tree for this window.
|
||||
@Published var surfaceTree: Ghostty.SplitNode? = nil
|
||||
|
||||
init(_ ghostty: Ghostty.App,
|
||||
position: SlideTerminalPosition = .top,
|
||||
baseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
||||
surfaceTree tree: Ghostty.SplitNode? = nil
|
||||
) {
|
||||
self.ghostty = ghostty
|
||||
self.position = position
|
||||
|
||||
super.init(window: nil)
|
||||
|
||||
// Initialize our initial surface.
|
||||
guard let ghostty_app = ghostty.app else { preconditionFailure("app must be loaded") }
|
||||
self.surfaceTree = tree ?? .leaf(.init(ghostty_app, baseConfig: base))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) is not supported for this view")
|
||||
}
|
||||
|
||||
// MARK: NSWindowController
|
||||
|
||||
override func windowDidLoad() {
|
||||
guard let window = self.window else { return }
|
||||
|
||||
// Make the window full width
|
||||
let screenFrame = NSScreen.main?.frame ?? .zero
|
||||
window.setFrame(NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: screenFrame.size.width,
|
||||
height: window.frame.size.height
|
||||
), display: false)
|
||||
// The slide window is not restorable (yet!). "Yet" because in theory we can
|
||||
// make this restorable, but it isn't currently implemented.
|
||||
window.isRestorable = false
|
||||
|
||||
slideWindowIn(window: window)
|
||||
// Setup our content
|
||||
window.contentView = NSHostingView(rootView: TerminalView(
|
||||
ghostty: self.ghostty,
|
||||
viewModel: self,
|
||||
delegate: self
|
||||
))
|
||||
|
||||
// Animate the window in
|
||||
slideWindowIn(window: window, from: position)
|
||||
}
|
||||
|
||||
private func slideWindowIn(window: NSWindow) {
|
||||
//MARK: TerminalViewDelegate
|
||||
|
||||
func cellSizeDidChange(to: NSSize) {
|
||||
guard ghostty.config.windowStepResize else { return }
|
||||
self.window?.contentResizeIncrements = to
|
||||
}
|
||||
|
||||
func surfaceTreeDidChange() {
|
||||
if (surfaceTree == nil) {
|
||||
self.window?.close()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Slide Logic
|
||||
|
||||
private func slideWindowIn(window: NSWindow, from position: SlideTerminalPosition) {
|
||||
guard let screen = NSScreen.main else { return }
|
||||
|
||||
// Determine our final position. Our final position is exactly
|
||||
// pinned against the top menu bar.
|
||||
let windowFrame = window.frame
|
||||
let finalY = screen.visibleFrame.maxY - windowFrame.height
|
||||
|
||||
// Move our window off screen to the top
|
||||
window.setFrameOrigin(.init(
|
||||
x: windowFrame.origin.x,
|
||||
y: screen.frame.maxY))
|
||||
|
||||
// Set the window invisible
|
||||
window.alphaValue = 0
|
||||
position.setInitial(in: window, on: screen)
|
||||
|
||||
// Move it to the visible position since animation requires this
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
@ -46,17 +84,7 @@ class SlideTerminalController: NSWindowController {
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = 0.3
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
|
||||
let animator = window.animator()
|
||||
animator.setFrame(.init(
|
||||
origin: .init(x: windowFrame.origin.x, y: finalY),
|
||||
size: windowFrame.size
|
||||
), display: true)
|
||||
animator.alphaValue = 1
|
||||
position.setFinal(in: window.animator(), on: screen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum SlideTerminalLocation {
|
||||
case top
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
import Cocoa
|
||||
|
||||
enum SlideTerminalPosition {
|
||||
case top
|
||||
|
||||
/// Set the initial state for a window for animating out of this position.
|
||||
func setInitial(in window: NSWindow, on screen: NSScreen) {
|
||||
// We always start invisible
|
||||
window.alphaValue = 0
|
||||
|
||||
// Position depends
|
||||
switch (self) {
|
||||
case .top:
|
||||
window.setFrame(.init(
|
||||
origin: .init(
|
||||
x: 0,
|
||||
y: screen.frame.maxY),
|
||||
size: .init(
|
||||
width: screen.frame.width,
|
||||
height: window.frame.height)
|
||||
), display: false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the final state for a window in this position.
|
||||
func setFinal(in window: NSWindow, on screen: NSScreen) {
|
||||
// We always end visible
|
||||
window.alphaValue = 1
|
||||
|
||||
// Position depends
|
||||
switch (self) {
|
||||
case .top:
|
||||
window.setFrame(.init(
|
||||
origin: .init(
|
||||
x: window.frame.origin.x,
|
||||
y: screen.visibleFrame.maxY - window.frame.height),
|
||||
size: window.frame.size
|
||||
), display: true)
|
||||
}
|
||||
}
|
||||
}
|
@ -23,9 +23,6 @@ class SlideTerminalWindow: NSWindow {
|
||||
// and lets us render off screen.
|
||||
self.level = .popUpMenu
|
||||
|
||||
self.isMovableByWindowBackground = true
|
||||
self.isMovable = true
|
||||
|
||||
self.collectionBehavior = [
|
||||
// We want this to be part of every space because it is a singleton.
|
||||
.canJoinAllSpaces,
|
||||
|
@ -18,6 +18,7 @@ protocol TerminalViewDelegate: AnyObject {
|
||||
/// not called initially.
|
||||
func surfaceTreeDidChange()
|
||||
|
||||
/// This is called when a split is zoomed.
|
||||
func zoomStateDidChange(to: Bool)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user