Normalize window controls and confirm close panel
This commit is contained in:
parent
ba68dc3637
commit
004a353fe5
7 changed files with 109 additions and 31 deletions
|
|
@ -35,6 +35,7 @@
|
|||
A5001207 /* UpdatePopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001217 /* UpdatePopoverView.swift */; };
|
||||
A5001208 /* UpdateTitlebarAccessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001218 /* UpdateTitlebarAccessory.swift */; };
|
||||
A5001209 /* WindowToolbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001219 /* WindowToolbarController.swift */; };
|
||||
A5001240 /* WindowDecorationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001241 /* WindowDecorationsController.swift */; };
|
||||
A5001100 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5001101 /* Assets.xcassets */; };
|
||||
A5001230 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A5001231 /* Sparkle */; };
|
||||
B9000002A1B2C3D4E5F60719 /* cmuxterm.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000001A1B2C3D4E5F60719 /* cmuxterm.swift */; };
|
||||
|
|
@ -119,6 +120,7 @@
|
|||
A5001219 /* WindowToolbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowToolbarController.swift; sourceTree = "<group>"; };
|
||||
A5001222 /* WindowAccessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowAccessor.swift; sourceTree = "<group>"; };
|
||||
A5001223 /* UpdateLogStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update/UpdateLogStore.swift; sourceTree = "<group>"; };
|
||||
A5001241 /* WindowDecorationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowDecorationsController.swift; sourceTree = "<group>"; };
|
||||
818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarResizeUITests.swift; sourceTree = "<group>"; };
|
||||
C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatePillUITests.swift; sourceTree = "<group>"; };
|
||||
A5001101 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
|
|
@ -238,6 +240,7 @@
|
|||
A5001217 /* UpdatePopoverView.swift */,
|
||||
A5001218 /* UpdateTitlebarAccessory.swift */,
|
||||
A5001219 /* WindowToolbarController.swift */,
|
||||
A5001241 /* WindowDecorationsController.swift */,
|
||||
A5001222 /* WindowAccessor.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
|
|
@ -404,6 +407,7 @@
|
|||
A5001207 /* UpdatePopoverView.swift in Sources */,
|
||||
A5001208 /* UpdateTitlebarAccessory.swift in Sources */,
|
||||
A5001209 /* WindowToolbarController.swift in Sources */,
|
||||
A5001240 /* WindowDecorationsController.swift in Sources */,
|
||||
A500120C /* WindowAccessor.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -510,7 +514,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 13;
|
||||
CURRENT_PROJECT_VERSION = 14;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
|
@ -526,7 +530,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.8.0;
|
||||
MARKETING_VERSION = 1.9.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-lc++",
|
||||
"-framework",
|
||||
|
|
@ -555,7 +559,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 13;
|
||||
CURRENT_PROJECT_VERSION = 14;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
|
@ -571,7 +575,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.8.0;
|
||||
MARKETING_VERSION = 1.9.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-lc++",
|
||||
"-framework",
|
||||
|
|
@ -624,10 +628,10 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 13;
|
||||
CURRENT_PROJECT_VERSION = 14;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.8.0;
|
||||
MARKETING_VERSION = 1.9.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cmuxterm.appuitests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
@ -641,10 +645,10 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 13;
|
||||
CURRENT_PROJECT_VERSION = 14;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.8.0;
|
||||
MARKETING_VERSION = 1.9.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cmuxterm.appuitests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
|
|||
private var workspaceObserver: NSObjectProtocol?
|
||||
private let updateController = UpdateController()
|
||||
private lazy var titlebarAccessoryController = UpdateTitlebarAccessoryController(viewModel: updateViewModel)
|
||||
private let windowDecorationsController = WindowDecorationsController()
|
||||
|
||||
var updateViewModel: UpdateViewModel {
|
||||
updateController.viewModel
|
||||
|
|
@ -31,6 +32,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
|
|||
configureUserNotifications()
|
||||
updateController.startUpdater()
|
||||
titlebarAccessoryController.start()
|
||||
windowDecorationsController.start()
|
||||
#if DEBUG
|
||||
UpdateTestSupport.applyIfNeeded(to: updateController.viewModel)
|
||||
if ProcessInfo.processInfo.environment["CMUX_UI_TEST_TRIGGER_UPDATE_CHECK"] == "1" {
|
||||
|
|
@ -133,6 +135,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
|
|||
titlebarAccessoryController.attach(to: window)
|
||||
}
|
||||
|
||||
func applyWindowDecorations(to window: NSWindow) {
|
||||
windowDecorationsController.apply(to: window)
|
||||
}
|
||||
|
||||
func toggleNotificationsPopover(animated: Bool = true) {
|
||||
titlebarAccessoryController.toggleNotificationsPopover(animated: animated)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ struct ContentView: View {
|
|||
.background(WindowAccessor { window in
|
||||
window.identifier = NSUserInterfaceItemIdentifier("cmux.main")
|
||||
AppDelegate.shared?.attachUpdateAccessory(to: window)
|
||||
AppDelegate.shared?.applyWindowDecorations(to: window)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ class GhosttyApp {
|
|||
GhosttyPasteboardHelper.writeString(fallback, to: location)
|
||||
}
|
||||
}
|
||||
runtimeConfig.close_surface_cb = { userdata, processAlive in
|
||||
runtimeConfig.close_surface_cb = { userdata, _ in
|
||||
guard let userdata else { return }
|
||||
let surfaceView = Unmanaged<GhosttyNSView>.fromOpaque(userdata).takeUnretainedValue()
|
||||
guard let tabId = surfaceView.tabId,
|
||||
|
|
@ -185,10 +185,15 @@ class GhosttyApp {
|
|||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
_ = AppDelegate.shared?.tabManager?.closeSurface(
|
||||
tabId: tabId,
|
||||
surfaceId: surfaceId
|
||||
)
|
||||
if let surface = surfaceView.terminalSurface,
|
||||
surface.needsConfirmClose() {
|
||||
AppDelegate.shared?.tabManager?.closePanelWithConfirmation(
|
||||
tabId: tabId,
|
||||
surfaceId: surfaceId
|
||||
)
|
||||
return
|
||||
}
|
||||
_ = AppDelegate.shared?.tabManager?.closeSurface(tabId: tabId, surfaceId: surfaceId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -373,20 +373,7 @@ class TabManager: ObservableObject {
|
|||
guard let selectedId = selectedTabId,
|
||||
let tab = tabs.first(where: { $0.id == selectedId }),
|
||||
let focusedSurfaceId = tab.focusedSurfaceId else { return }
|
||||
guard tab.splitTree.isSplit else {
|
||||
closeTabIfRunningProcess(tab)
|
||||
return
|
||||
}
|
||||
|
||||
let focusedSurface = tab.surface(for: focusedSurfaceId)
|
||||
if focusedSurface?.needsConfirmClose() == true {
|
||||
guard confirmClose(
|
||||
title: "Close panel?",
|
||||
message: "This will close the current split panel in this tab."
|
||||
) else { return }
|
||||
}
|
||||
|
||||
_ = closeSurface(tabId: selectedId, surfaceId: focusedSurfaceId)
|
||||
closePanelWithConfirmation(tab: tab, surfaceId: focusedSurfaceId)
|
||||
}
|
||||
|
||||
func closeCurrentTabWithConfirmation() {
|
||||
|
|
@ -421,6 +408,28 @@ class TabManager: ObservableObject {
|
|||
closeTab(tab)
|
||||
}
|
||||
|
||||
private func closePanelWithConfirmation(tab: Tab, surfaceId: UUID) {
|
||||
guard tab.splitTree.isSplit else {
|
||||
closeTabIfRunningProcess(tab)
|
||||
return
|
||||
}
|
||||
|
||||
let surface = tab.surface(for: surfaceId)
|
||||
if surface?.needsConfirmClose() == true {
|
||||
guard confirmClose(
|
||||
title: "Close panel?",
|
||||
message: "This will close the current split panel in this tab."
|
||||
) else { return }
|
||||
}
|
||||
|
||||
_ = closeSurface(tabId: tab.id, surfaceId: surfaceId)
|
||||
}
|
||||
|
||||
func closePanelWithConfirmation(tabId: UUID, surfaceId: UUID) {
|
||||
guard let tab = tabs.first(where: { $0.id == tabId }) else { return }
|
||||
closePanelWithConfirmation(tab: tab, surfaceId: surfaceId)
|
||||
}
|
||||
|
||||
private func tabNeedsConfirmClose(_ tab: Tab) -> Bool {
|
||||
guard let root = tab.splitTree.root else { return false }
|
||||
return root.leaves().contains { $0.needsConfirmClose() }
|
||||
|
|
|
|||
53
Sources/WindowDecorationsController.swift
Normal file
53
Sources/WindowDecorationsController.swift
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import AppKit
|
||||
|
||||
final class WindowDecorationsController {
|
||||
private var observers: [NSObjectProtocol] = []
|
||||
private var didStart = false
|
||||
|
||||
func start() {
|
||||
guard !didStart else { return }
|
||||
didStart = true
|
||||
attachToExistingWindows()
|
||||
installObservers()
|
||||
}
|
||||
|
||||
func apply(to window: NSWindow) {
|
||||
let shouldHideButtons = shouldHideTrafficLights(for: window)
|
||||
hideStandardButtons(on: window, hidden: shouldHideButtons)
|
||||
}
|
||||
|
||||
private func installObservers() {
|
||||
let center = NotificationCenter.default
|
||||
let handler: (Notification) -> Void = { [weak self] notification in
|
||||
guard let self, let window = notification.object as? NSWindow else { return }
|
||||
self.apply(to: window)
|
||||
}
|
||||
observers.append(center.addObserver(forName: NSWindow.didBecomeKeyNotification, object: nil, queue: .main, using: handler))
|
||||
observers.append(center.addObserver(forName: NSWindow.didBecomeMainNotification, object: nil, queue: .main, using: handler))
|
||||
}
|
||||
|
||||
private func attachToExistingWindows() {
|
||||
for window in NSApp.windows {
|
||||
apply(to: window)
|
||||
}
|
||||
}
|
||||
|
||||
private func hideStandardButtons(on window: NSWindow, hidden: Bool) {
|
||||
window.standardWindowButton(.closeButton)?.isHidden = hidden
|
||||
window.standardWindowButton(.miniaturizeButton)?.isHidden = hidden
|
||||
window.standardWindowButton(.zoomButton)?.isHidden = hidden
|
||||
}
|
||||
|
||||
private func shouldHideTrafficLights(for window: NSWindow) -> Bool {
|
||||
if window.isSheet {
|
||||
return true
|
||||
}
|
||||
if window is NSPanel {
|
||||
return true
|
||||
}
|
||||
if window.styleMask.contains(.utilityWindow) || window.styleMask.contains(.docModalWindow) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -310,9 +310,9 @@ private final class AboutWindowController: NSWindowController, NSWindowDelegate
|
|||
static let shared = AboutWindowController()
|
||||
|
||||
private init() {
|
||||
let window = NSWindow(
|
||||
let window = NSPanel(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 360, height: 520),
|
||||
styleMask: [.titled, .closable],
|
||||
styleMask: [.titled, .closable, .utilityWindow],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
|
|
@ -321,11 +321,10 @@ private final class AboutWindowController: NSWindowController, NSWindowDelegate
|
|||
window.titlebarAppearsTransparent = true
|
||||
window.isMovableByWindowBackground = true
|
||||
window.isReleasedWhenClosed = false
|
||||
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||
window.standardWindowButton(.zoomButton)?.isHidden = true
|
||||
window.identifier = NSUserInterfaceItemIdentifier("cmux.about")
|
||||
window.center()
|
||||
window.contentView = NSHostingView(rootView: AboutPanelView())
|
||||
AppDelegate.shared?.applyWindowDecorations(to: window)
|
||||
super.init(window: window)
|
||||
window.delegate = self
|
||||
}
|
||||
|
|
@ -604,5 +603,6 @@ private struct SettingsRootView: View {
|
|||
guard identifier.hasPrefix("cmux.") else { continue }
|
||||
window.removeTitlebarAccessoryViewController(at: index)
|
||||
}
|
||||
AppDelegate.shared?.applyWindowDecorations(to: window)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue