mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: working on custom split view
This commit is contained in:
@ -12,10 +12,12 @@
|
|||||||
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB529B6F47F0055DE60 /* AppState.swift */; };
|
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB529B6F47F0055DE60 /* AppState.swift */; };
|
||||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB729B6F53A0055DE60 /* Package.swift */; };
|
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB729B6F53A0055DE60 /* Package.swift */; };
|
||||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */; };
|
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */; };
|
||||||
A55B7BBE29B701360055DE60 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BBD29B701360055DE60 /* SplitView.swift */; };
|
A55B7BBE29B701360055DE60 /* Ghostty.SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BBD29B701360055DE60 /* Ghostty.SplitView.swift */; };
|
||||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
|
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
|
||||||
A5B30535299BEAAA0047F10C /* GhosttyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B30534299BEAAA0047F10C /* GhosttyApp.swift */; };
|
A5B30535299BEAAA0047F10C /* GhosttyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B30534299BEAAA0047F10C /* GhosttyApp.swift */; };
|
||||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||||
|
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDB29B8009000646FDA /* SplitView.swift */; };
|
||||||
|
A5CEAFDE29B8058B00646FDA /* SplitView.Splitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Splitter.swift */; };
|
||||||
A5D495A2299BEC7E00DD1313 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
|
A5D495A2299BEC7E00DD1313 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@ -25,12 +27,14 @@
|
|||||||
A55B7BB529B6F47F0055DE60 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
A55B7BB529B6F47F0055DE60 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
||||||
A55B7BB729B6F53A0055DE60 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
A55B7BB729B6F53A0055DE60 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
||||||
A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
|
A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
|
||||||
A55B7BBD29B701360055DE60 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
|
A55B7BBD29B701360055DE60 /* Ghostty.SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.SplitView.swift; sourceTree = "<group>"; };
|
||||||
A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
A5B30534299BEAAA0047F10C /* GhosttyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyApp.swift; sourceTree = "<group>"; };
|
A5B30534299BEAAA0047F10C /* GhosttyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyApp.swift; sourceTree = "<group>"; };
|
||||||
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
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>"; };
|
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
||||||
|
A5CEAFDB29B8009000646FDA /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
|
||||||
|
A5CEAFDD29B8058B00646FDA /* SplitView.Splitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Splitter.swift; sourceTree = "<group>"; };
|
||||||
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
|
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@ -50,6 +54,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A5D495A0299BEC2200DD1313 /* Preview Content */,
|
A5D495A0299BEC2200DD1313 /* Preview Content */,
|
||||||
|
A5CEAFDA29B8005900646FDA /* SplitView */,
|
||||||
A55B7BB429B6F4410055DE60 /* Ghostty */,
|
A55B7BB429B6F4410055DE60 /* Ghostty */,
|
||||||
A5B30534299BEAAA0047F10C /* GhosttyApp.swift */,
|
A5B30534299BEAAA0047F10C /* GhosttyApp.swift */,
|
||||||
A535B9D9299C569B0017E2E4 /* ErrorView.swift */,
|
A535B9D9299C569B0017E2E4 /* ErrorView.swift */,
|
||||||
@ -65,7 +70,7 @@
|
|||||||
A55B7BB729B6F53A0055DE60 /* Package.swift */,
|
A55B7BB729B6F53A0055DE60 /* Package.swift */,
|
||||||
A55B7BB529B6F47F0055DE60 /* AppState.swift */,
|
A55B7BB529B6F47F0055DE60 /* AppState.swift */,
|
||||||
A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */,
|
A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */,
|
||||||
A55B7BBD29B701360055DE60 /* SplitView.swift */,
|
A55B7BBD29B701360055DE60 /* Ghostty.SplitView.swift */,
|
||||||
);
|
);
|
||||||
path = Ghostty;
|
path = Ghostty;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -89,6 +94,15 @@
|
|||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A5CEAFDA29B8005900646FDA /* SplitView */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A5CEAFDB29B8009000646FDA /* SplitView.swift */,
|
||||||
|
A5CEAFDD29B8058B00646FDA /* SplitView.Splitter.swift */,
|
||||||
|
);
|
||||||
|
path = SplitView;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A5D495A0299BEC2200DD1313 /* Preview Content */ = {
|
A5D495A0299BEC2200DD1313 /* Preview Content */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -176,11 +190,13 @@
|
|||||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
||||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
|
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
|
||||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
||||||
A55B7BBE29B701360055DE60 /* SplitView.swift in Sources */,
|
A55B7BBE29B701360055DE60 /* Ghostty.SplitView.swift in Sources */,
|
||||||
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */,
|
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */,
|
||||||
|
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||||
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
|
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
|
||||||
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
||||||
A5B30535299BEAAA0047F10C /* GhosttyApp.swift in Sources */,
|
A5B30535299BEAAA0047F10C /* GhosttyApp.swift in Sources */,
|
||||||
|
A5CEAFDE29B8058B00646FDA /* SplitView.Splitter.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -21,8 +21,15 @@ struct GhosttyApp: App {
|
|||||||
case .error:
|
case .error:
|
||||||
ErrorView()
|
ErrorView()
|
||||||
case .ready:
|
case .ready:
|
||||||
Ghostty.TerminalSplitView()
|
SplitView(.horizontal, left: {
|
||||||
|
Color.green
|
||||||
|
}, right: {
|
||||||
|
Color.red
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
Ghostty.Terminal()
|
||||||
.ghosttyApp(ghostty.app!)
|
.ghosttyApp(ghostty.app!)
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}.commands {
|
}.commands {
|
||||||
CommandGroup(after: .newItem) {
|
CommandGroup(after: .newItem) {
|
||||||
|
67
macos/Sources/SplitView/SplitView.Splitter.swift
Normal file
67
macos/Sources/SplitView/SplitView.Splitter.swift
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SplitView {
|
||||||
|
struct Splitter: View {
|
||||||
|
let direction: Direction
|
||||||
|
let visibleSize: CGFloat
|
||||||
|
let invisibleSize: CGFloat
|
||||||
|
|
||||||
|
private var visibleWidth: CGFloat? {
|
||||||
|
switch (direction) {
|
||||||
|
case .horizontal:
|
||||||
|
return visibleSize
|
||||||
|
case .vertical:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var visibleHeight: CGFloat? {
|
||||||
|
switch (direction) {
|
||||||
|
case .horizontal:
|
||||||
|
return nil
|
||||||
|
case .vertical:
|
||||||
|
return visibleSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var invisibleWidth: CGFloat? {
|
||||||
|
switch (direction) {
|
||||||
|
case .horizontal:
|
||||||
|
return visibleSize + invisibleSize
|
||||||
|
case .vertical:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var invisibleHeight: CGFloat? {
|
||||||
|
switch (direction) {
|
||||||
|
case .horizontal:
|
||||||
|
return nil
|
||||||
|
case .vertical:
|
||||||
|
return visibleSize + invisibleSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.clear
|
||||||
|
.frame(width: invisibleWidth, height: invisibleHeight)
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.gray)
|
||||||
|
.frame(width: visibleWidth, height: visibleHeight)
|
||||||
|
}
|
||||||
|
.onHover { isHovered in
|
||||||
|
if (isHovered) {
|
||||||
|
switch (direction) {
|
||||||
|
case .horizontal:
|
||||||
|
NSCursor.resizeLeftRight.push()
|
||||||
|
case .vertical:
|
||||||
|
NSCursor.resizeUpDown.push()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NSCursor.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
macos/Sources/SplitView/SplitView.swift
Normal file
87
macos/Sources/SplitView/SplitView.swift
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SplitView<L: View, R: View>: View {
|
||||||
|
let direction: Direction
|
||||||
|
let left: L
|
||||||
|
let right: R
|
||||||
|
|
||||||
|
private let splitterVisibleSize: CGFloat = 5
|
||||||
|
private let splitterInvisibleSize: CGFloat = 5
|
||||||
|
|
||||||
|
@State var split: CGFloat = 0.5
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geo in
|
||||||
|
let leftRect = self.leftRect(for: geo.size)
|
||||||
|
let rightRect = self.rightRect(for: geo.size, leftRect: leftRect)
|
||||||
|
let splitterPoint = self.splitterPoint(for: geo.size, leftRect: leftRect)
|
||||||
|
|
||||||
|
ZStack(alignment: .topLeading) {
|
||||||
|
left
|
||||||
|
.frame(width: leftRect.size.width, height: leftRect.size.height)
|
||||||
|
.offset(x: leftRect.origin.x, y: leftRect.origin.y)
|
||||||
|
right
|
||||||
|
.frame(width: rightRect.size.width, height: rightRect.size.height)
|
||||||
|
.offset(x: rightRect.origin.x, y: rightRect.origin.y)
|
||||||
|
Splitter(direction: direction, visibleSize: splitterVisibleSize, invisibleSize: splitterInvisibleSize)
|
||||||
|
.position(splitterPoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func leftRect(for size: CGSize) -> CGRect {
|
||||||
|
// Initially the rect is the full size
|
||||||
|
var result = CGRect(x: 0, y: 0, width: size.width, height: size.height)
|
||||||
|
switch (direction) {
|
||||||
|
case .horizontal:
|
||||||
|
result.size.width = result.size.width * split
|
||||||
|
result.size.width -= splitterVisibleSize / 2
|
||||||
|
|
||||||
|
case .vertical:
|
||||||
|
result.size.height = result.size.height * split
|
||||||
|
result.size.height -= splitterVisibleSize / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func rightRect(for size: CGSize, leftRect: CGRect) -> CGRect {
|
||||||
|
// Initially the rect is the full size
|
||||||
|
var result = CGRect(x: 0, y: 0, width: size.width, height: size.height)
|
||||||
|
switch (direction) {
|
||||||
|
case .horizontal:
|
||||||
|
// For horizontal layouts we offset the starting X by the left rect
|
||||||
|
// and make the width fit the remaining space.
|
||||||
|
result.origin.x += leftRect.size.width
|
||||||
|
result.origin.x += splitterVisibleSize / 2
|
||||||
|
result.size.width -= result.origin.x
|
||||||
|
|
||||||
|
case .vertical:
|
||||||
|
assert(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitterPoint(for size: CGSize, leftRect: CGRect) -> CGPoint {
|
||||||
|
switch (direction) {
|
||||||
|
case .horizontal:
|
||||||
|
return CGPoint(x: leftRect.size.width, y: size.height / 2)
|
||||||
|
|
||||||
|
case .vertical:
|
||||||
|
return CGPoint(x: size.width / 2, y: leftRect.size.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ direction: Direction, @ViewBuilder left: (() -> L), @ViewBuilder right: (() -> R)) {
|
||||||
|
self.direction = direction
|
||||||
|
self.left = left()
|
||||||
|
self.right = right()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SplitView {
|
||||||
|
enum Direction {
|
||||||
|
case horizontal, vertical
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user