macos: working on custom split view

This commit is contained in:
Mitchell Hashimoto
2023-03-07 16:22:00 -08:00
parent 1faca5972f
commit d00794de8e
5 changed files with 183 additions and 6 deletions

View File

@ -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;
}; };

View File

@ -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) {

View 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()
}
}
}
}
}

View 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
}
}