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 */; };
|
A5CBD05C2CA0C5C70017A1AE /* SlideTerminal.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CBD05B2CA0C5C70017A1AE /* SlideTerminal.xib */; };
|
||||||
A5CBD05E2CA0C5EC0017A1AE /* SlideTerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD05D2CA0C5E70017A1AE /* SlideTerminalController.swift */; };
|
A5CBD05E2CA0C5EC0017A1AE /* SlideTerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD05D2CA0C5E70017A1AE /* SlideTerminalController.swift */; };
|
||||||
A5CBD0602CA0C90A0017A1AE /* SlideTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD05F2CA0C9080017A1AE /* SlideTerminalWindow.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 */; };
|
A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */; };
|
||||||
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36142C9CDA03004D6760 /* View+Extension.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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationErrors.xib; sourceTree = "<group>"; };
|
||||||
@ -393,6 +395,7 @@
|
|||||||
children = (
|
children = (
|
||||||
A5CBD05B2CA0C5C70017A1AE /* SlideTerminal.xib */,
|
A5CBD05B2CA0C5C70017A1AE /* SlideTerminal.xib */,
|
||||||
A5CBD05D2CA0C5E70017A1AE /* SlideTerminalController.swift */,
|
A5CBD05D2CA0C5E70017A1AE /* SlideTerminalController.swift */,
|
||||||
|
A5CBD0632CA122E70017A1AE /* SlideTerminalPosition.swift */,
|
||||||
A5CBD05F2CA0C9080017A1AE /* SlideTerminalWindow.swift */,
|
A5CBD05F2CA0C9080017A1AE /* SlideTerminalWindow.swift */,
|
||||||
);
|
);
|
||||||
path = SlideTerminal;
|
path = SlideTerminal;
|
||||||
@ -548,6 +551,7 @@
|
|||||||
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
|
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
|
||||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
||||||
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */,
|
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */,
|
||||||
|
A5CBD0642CA122E70017A1AE /* SlideTerminalPosition.swift in Sources */,
|
||||||
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
||||||
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */,
|
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */,
|
||||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||||
|
@ -170,7 +170,7 @@ class AppDelegate: NSObject,
|
|||||||
//terminalManager.newWindow()
|
//terminalManager.newWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
foo = SlideTerminalController(window: nil)
|
foo = SlideTerminalController(ghostty, baseConfig: nil)
|
||||||
foo?.showWindow(self)
|
foo?.showWindow(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,39 +4,77 @@ import SwiftUI
|
|||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
/// Controller for the slide-style terminal.
|
/// Controller for the slide-style terminal.
|
||||||
class SlideTerminalController: NSWindowController {
|
class SlideTerminalController: NSWindowController, TerminalViewDelegate, TerminalViewModel {
|
||||||
override var windowNibName: NSNib.Name? { "SlideTerminal" }
|
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() {
|
override func windowDidLoad() {
|
||||||
guard let window = self.window else { return }
|
guard let window = self.window else { return }
|
||||||
|
|
||||||
// Make the window full width
|
// The slide window is not restorable (yet!). "Yet" because in theory we can
|
||||||
let screenFrame = NSScreen.main?.frame ?? .zero
|
// make this restorable, but it isn't currently implemented.
|
||||||
window.setFrame(NSRect(
|
window.isRestorable = false
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: screenFrame.size.width,
|
|
||||||
height: window.frame.size.height
|
|
||||||
), display: 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 }
|
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
|
// Move our window off screen to the top
|
||||||
window.setFrameOrigin(.init(
|
position.setInitial(in: window, on: screen)
|
||||||
x: windowFrame.origin.x,
|
|
||||||
y: screen.frame.maxY))
|
|
||||||
|
|
||||||
// Set the window invisible
|
|
||||||
window.alphaValue = 0
|
|
||||||
|
|
||||||
// Move it to the visible position since animation requires this
|
// Move it to the visible position since animation requires this
|
||||||
window.makeKeyAndOrderFront(nil)
|
window.makeKeyAndOrderFront(nil)
|
||||||
@ -46,17 +84,7 @@ class SlideTerminalController: NSWindowController {
|
|||||||
NSAnimationContext.runAnimationGroup { context in
|
NSAnimationContext.runAnimationGroup { context in
|
||||||
context.duration = 0.3
|
context.duration = 0.3
|
||||||
context.timingFunction = .init(name: .easeIn)
|
context.timingFunction = .init(name: .easeIn)
|
||||||
|
position.setFinal(in: window.animator(), on: screen)
|
||||||
let animator = window.animator()
|
|
||||||
animator.setFrame(.init(
|
|
||||||
origin: .init(x: windowFrame.origin.x, y: finalY),
|
|
||||||
size: windowFrame.size
|
|
||||||
), display: true)
|
|
||||||
animator.alphaValue = 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// and lets us render off screen.
|
||||||
self.level = .popUpMenu
|
self.level = .popUpMenu
|
||||||
|
|
||||||
self.isMovableByWindowBackground = true
|
|
||||||
self.isMovable = true
|
|
||||||
|
|
||||||
self.collectionBehavior = [
|
self.collectionBehavior = [
|
||||||
// We want this to be part of every space because it is a singleton.
|
// We want this to be part of every space because it is a singleton.
|
||||||
.canJoinAllSpaces,
|
.canJoinAllSpaces,
|
||||||
|
@ -18,6 +18,7 @@ protocol TerminalViewDelegate: AnyObject {
|
|||||||
/// not called initially.
|
/// not called initially.
|
||||||
func surfaceTreeDidChange()
|
func surfaceTreeDidChange()
|
||||||
|
|
||||||
|
/// This is called when a split is zoomed.
|
||||||
func zoomStateDidChange(to: Bool)
|
func zoomStateDidChange(to: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user