Add floating close button to split panes with subtle design

- Added HoverCloseButton class with hover effects
- Close button appears in top-right corner of each split pane
- Subtle dark semi-transparent design that brightens on hover
- Container view approach for proper layout and positioning
- Updated help and keyboard shortcuts documentation
- Removed non-working Cmd+Shift+W from help displays
This commit is contained in:
2026-01-31 23:44:32 +00:00
parent cae91fec8c
commit faad3c3981
4 changed files with 75 additions and 23 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -1,5 +1,44 @@
import Cocoa import Cocoa
// MARK: - Hover Close Button
class HoverCloseButton: NSButton {
override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
NSAnimationContext.runAnimationGroup { context in
context.duration = 0.15
self.layer?.backgroundColor = NSColor(white: 0.3, alpha: 0.85).cgColor
self.contentTintColor = NSColor.white
}
}
override func mouseExited(with event: NSEvent) {
super.mouseExited(with: event)
NSAnimationContext.runAnimationGroup { context in
context.duration = 0.15
self.layer?.backgroundColor = NSColor(white: 0.2, alpha: 0.6).cgColor
self.contentTintColor = NSColor(white: 0.8, alpha: 0.9)
}
}
override func updateTrackingAreas() {
super.updateTrackingAreas()
// Remove old tracking areas
for trackingArea in trackingAreas {
removeTrackingArea(trackingArea)
}
// Add new tracking area
let trackingArea = NSTrackingArea(
rect: bounds,
options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect],
owner: self,
userInfo: nil
)
addTrackingArea(trackingArea)
}
}
class MainWindowController: NSWindowController { class MainWindowController: NSWindowController {
var tabManager = TabManager() var tabManager = TabManager()
var tabBar: NSStackView! var tabBar: NSStackView!
@@ -511,9 +550,8 @@ class MainWindowController: NSWindowController {
File: File:
Cmd+T - New Tab Cmd+T - New Tab
Cmd+W - Close Tab Cmd+W - Close Tab
Cmd+D - Split Pane Vertically Cmd+D - Split Pane Vertically (side-by-side)
Cmd+Shift+D - Split Pane Horizontally Cmd+Shift+D - Split Pane Horizontally (top-bottom)
Cmd+Shift+W - Close Pane
Cmd+K - Clear Screen Cmd+K - Clear Screen
Cmd+Shift+K - Clear Scrollback Cmd+Shift+K - Clear Scrollback
@@ -538,7 +576,9 @@ class MainWindowController: NSWindowController {
Terminal: Terminal:
Tab - Accept Auto-suggestion Tab - Accept Auto-suggestion
Up/Down - Navigate Command History
Double-click Tab - Rename Tab Double-click Tab - Rename Tab
Click × button - Close split pane
""" """
alert.alertStyle = .informational alert.alertStyle = .informational
alert.addButton(withTitle: "OK") alert.addButton(withTitle: "OK")
@@ -563,10 +603,10 @@ class MainWindowController: NSWindowController {
splitView?.dividerStyle = .thin splitView?.dividerStyle = .thin
splitView?.autoresizingMask = [.width, .height] splitView?.autoresizingMask = [.width, .height]
// Move current session to split view // Move current session to split view with close button
currentSession.scrollView.removeFromSuperview() currentSession.scrollView.removeFromSuperview()
addCloseButtonToPane(currentSession.scrollView, index: 0) let containerView = addCloseButtonToPane(currentSession.scrollView, index: 0)
splitView?.addArrangedSubview(currentSession.scrollView) splitView?.addArrangedSubview(containerView)
panes = [currentSession] panes = [currentSession]
} }
@@ -575,8 +615,8 @@ class MainWindowController: NSWindowController {
newSession.setupTextView(frame: CGRect(x: 0, y: 0, width: contentView.bounds.width / 2, height: contentView.bounds.height)) newSession.setupTextView(frame: CGRect(x: 0, y: 0, width: contentView.bounds.width / 2, height: contentView.bounds.height))
newSession.startShell() newSession.startShell()
addCloseButtonToPane(newSession.scrollView, index: panes.count) let newContainerView = addCloseButtonToPane(newSession.scrollView, index: panes.count)
splitView?.addArrangedSubview(newSession.scrollView) splitView?.addArrangedSubview(newContainerView)
panes.append(newSession) panes.append(newSession)
activePaneIndex = panes.count - 1 activePaneIndex = panes.count - 1
@@ -607,10 +647,10 @@ class MainWindowController: NSWindowController {
splitView?.dividerStyle = .thin splitView?.dividerStyle = .thin
splitView?.autoresizingMask = [.width, .height] splitView?.autoresizingMask = [.width, .height]
// Move current session to split view // Move current session to split view with close button
currentSession.scrollView.removeFromSuperview() currentSession.scrollView.removeFromSuperview()
addCloseButtonToPane(currentSession.scrollView, index: 0) let containerView = addCloseButtonToPane(currentSession.scrollView, index: 0)
splitView?.addArrangedSubview(currentSession.scrollView) splitView?.addArrangedSubview(containerView)
panes = [currentSession] panes = [currentSession]
} }
@@ -619,8 +659,8 @@ class MainWindowController: NSWindowController {
newSession.setupTextView(frame: CGRect(x: 0, y: 0, width: contentView.bounds.width, height: contentView.bounds.height / 2)) newSession.setupTextView(frame: CGRect(x: 0, y: 0, width: contentView.bounds.width, height: contentView.bounds.height / 2))
newSession.startShell() newSession.startShell()
addCloseButtonToPane(newSession.scrollView, index: panes.count) let newContainerView = addCloseButtonToPane(newSession.scrollView, index: panes.count)
splitView?.addArrangedSubview(newSession.scrollView) splitView?.addArrangedSubview(newContainerView)
panes.append(newSession) panes.append(newSession)
activePaneIndex = panes.count - 1 activePaneIndex = panes.count - 1
@@ -633,29 +673,39 @@ class MainWindowController: NSWindowController {
newSession.textView.window?.makeFirstResponder(newSession.textView) newSession.textView.window?.makeFirstResponder(newSession.textView)
} }
private func addCloseButtonToPane(_ scrollView: NSScrollView, index: Int) { private func addCloseButtonToPane(_ scrollView: NSScrollView, index: Int) -> NSView {
// Remove any existing close button // Create a container view that will hold both the scroll view and the close button
scrollView.subviews.filter { $0.tag == 9999 }.forEach { $0.removeFromSuperview() } let containerView = NSView(frame: scrollView.frame)
containerView.autoresizingMask = [.width, .height]
containerView.wantsLayer = true
// Create close button // Adjust scroll view frame to fill container
let closeButton = NSButton(frame: NSRect(x: scrollView.bounds.width - 30, y: scrollView.bounds.height - 30, width: 24, height: 24)) scrollView.frame = containerView.bounds
scrollView.autoresizingMask = [.width, .height]
containerView.addSubview(scrollView)
// Create close button with subtle styling
let closeButton = HoverCloseButton(frame: NSRect(x: containerView.bounds.width - 35, y: containerView.bounds.height - 35, width: 24, height: 24))
closeButton.title = "×" closeButton.title = "×"
closeButton.bezelStyle = .circular closeButton.bezelStyle = .circular
closeButton.font = NSFont.systemFont(ofSize: 16, weight: .bold) closeButton.font = NSFont.systemFont(ofSize: 16, weight: .medium)
closeButton.tag = 9999 // Special tag to identify close buttons closeButton.tag = 9999
closeButton.target = self closeButton.target = self
closeButton.action = #selector(closePaneFromButton(_:)) closeButton.action = #selector(closePaneFromButton(_:))
closeButton.toolTip = "Close pane (Cmd+Shift+W)" closeButton.toolTip = "Close pane (Cmd+Shift+W)"
closeButton.autoresizingMask = [.minXMargin, .minYMargin] closeButton.autoresizingMask = [.minXMargin, .minYMargin]
closeButton.wantsLayer = true closeButton.wantsLayer = true
closeButton.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.5).cgColor closeButton.layer?.backgroundColor = NSColor(white: 0.2, alpha: 0.6).cgColor
closeButton.layer?.cornerRadius = 12 closeButton.layer?.cornerRadius = 12
closeButton.contentTintColor = .white closeButton.contentTintColor = NSColor(white: 0.8, alpha: 0.9)
// Store the pane index // Store the pane index
objc_setAssociatedObject(closeButton, "paneIndex", index, .OBJC_ASSOCIATION_RETAIN) objc_setAssociatedObject(closeButton, "paneIndex", index, .OBJC_ASSOCIATION_RETAIN)
scrollView.addSubview(closeButton) // Add button on top of scroll view
containerView.addSubview(closeButton)
return containerView
} }
@objc func closePaneFromButton(_ sender: NSButton) { @objc func closePaneFromButton(_ sender: NSButton) {

View File

@@ -568,6 +568,8 @@ class TerminalSession: NSObject, NSTextViewDelegate, NSMenuDelegate {
Keyboard Shortcuts: Keyboard Shortcuts:
• Cmd+T - New tab • Cmd+T - New tab
• Cmd+W - Close tab • Cmd+W - Close tab
• Cmd+D - Split pane vertically (side-by-side)
• Cmd+Shift+D - Split pane horizontally (top-bottom)
• Cmd+R - Search command history (fuzzy search) • Cmd+R - Search command history (fuzzy search)
• Cmd+K - AI command generator • Cmd+K - AI command generator
• Cmd+P - Command snippets • Cmd+P - Command snippets