Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions Sources/Bonsplit/Internal/Styling/TabBarColors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SwiftUI
import AppKit

/// Native macOS colors for the tab bar
enum TabBarColors {
public enum TabBarColors {
private enum Constants {
static let darkTextAlpha: CGFloat = 0.82
static let darkSecondaryTextAlpha: CGFloat = 0.62
Expand Down Expand Up @@ -48,35 +48,35 @@ enum TabBarColors {
return NSColor.white.withAlphaComponent(alpha)
}

static func paneBackground(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func paneBackground(for appearance: BonsplitConfiguration.Appearance) -> Color {
Color(nsColor: effectiveBackgroundColor(for: appearance, fallback: .textBackgroundColor))
}

static func nsColorPaneBackground(for appearance: BonsplitConfiguration.Appearance) -> NSColor {
public static func nsColorPaneBackground(for appearance: BonsplitConfiguration.Appearance) -> NSColor {
effectiveBackgroundColor(for: appearance, fallback: .textBackgroundColor)
}

// MARK: - Tab Bar Background

static var barBackground: Color {
public static var barBackground: Color {
Color(nsColor: .windowBackgroundColor)
}

static func barBackground(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func barBackground(for appearance: BonsplitConfiguration.Appearance) -> Color {
Color(nsColor: effectiveBackgroundColor(for: appearance, fallback: .windowBackgroundColor))
}

static var barMaterial: Material {
public static var barMaterial: Material {
.bar
}

// MARK: - Tab States

static var activeTabBackground: Color {
public static var activeTabBackground: Color {
Color(nsColor: .controlBackgroundColor)
}

static func activeTabBackground(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func activeTabBackground(for appearance: BonsplitConfiguration.Appearance) -> Color {
guard let custom = chromeBackgroundColor(for: appearance) else {
return activeTabBackground
}
Expand All @@ -86,11 +86,11 @@ enum TabBarColors {
return Color(nsColor: adjusted)
}

static var hoveredTabBackground: Color {
public static var hoveredTabBackground: Color {
Color(nsColor: .controlBackgroundColor).opacity(0.5)
}

static func hoveredTabBackground(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func hoveredTabBackground(for appearance: BonsplitConfiguration.Appearance) -> Color {
guard let custom = chromeBackgroundColor(for: appearance) else {
return hoveredTabBackground
}
Expand All @@ -100,41 +100,41 @@ enum TabBarColors {
return Color(nsColor: adjusted.withAlphaComponent(0.78))
}

static var inactiveTabBackground: Color {
public static var inactiveTabBackground: Color {
.clear
}

// MARK: - Text Colors

static var activeText: Color {
public static var activeText: Color {
Color(nsColor: .labelColor)
}

static func activeText(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func activeText(for appearance: BonsplitConfiguration.Appearance) -> Color {
Color(nsColor: effectiveTextColor(for: appearance, secondary: false))
}

static func nsColorActiveText(for appearance: BonsplitConfiguration.Appearance) -> NSColor {
public static func nsColorActiveText(for appearance: BonsplitConfiguration.Appearance) -> NSColor {
effectiveTextColor(for: appearance, secondary: false)
}

static var inactiveText: Color {
public static var inactiveText: Color {
Color(nsColor: .secondaryLabelColor)
}

static func inactiveText(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func inactiveText(for appearance: BonsplitConfiguration.Appearance) -> Color {
Color(nsColor: effectiveTextColor(for: appearance, secondary: true))
}

static func nsColorInactiveText(for appearance: BonsplitConfiguration.Appearance) -> NSColor {
public static func nsColorInactiveText(for appearance: BonsplitConfiguration.Appearance) -> NSColor {
effectiveTextColor(for: appearance, secondary: true)
}

static func splitActionIcon(for appearance: BonsplitConfiguration.Appearance, isPressed: Bool) -> Color {
public static func splitActionIcon(for appearance: BonsplitConfiguration.Appearance, isPressed: Bool) -> Color {
Color(nsColor: nsColorSplitActionIcon(for: appearance, isPressed: isPressed))
}

static func nsColorSplitActionIcon(
public static func nsColorSplitActionIcon(
for appearance: BonsplitConfiguration.Appearance,
isPressed: Bool
) -> NSColor {
Expand All @@ -143,15 +143,15 @@ enum TabBarColors {

// MARK: - Borders & Indicators

static var separator: Color {
public static var separator: Color {
Color(nsColor: .separatorColor)
}

static func separator(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func separator(for appearance: BonsplitConfiguration.Appearance) -> Color {
Color(nsColor: nsColorSeparator(for: appearance))
}

static func nsColorSeparator(for appearance: BonsplitConfiguration.Appearance) -> NSColor {
public static func nsColorSeparator(for appearance: BonsplitConfiguration.Appearance) -> NSColor {
if let explicit = chromeBorderColor(for: appearance) {
return explicit
}
Expand All @@ -166,40 +166,40 @@ enum TabBarColors {
return tone.withAlphaComponent(alpha)
}

static var dropIndicator: Color {
public static var dropIndicator: Color {
Color.accentColor
}

static func dropIndicator(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func dropIndicator(for appearance: BonsplitConfiguration.Appearance) -> Color {
_ = appearance
return dropIndicator
}

static var focusRing: Color {
public static var focusRing: Color {
Color.accentColor.opacity(0.5)
}

static var dirtyIndicator: Color {
public static var dirtyIndicator: Color {
Color(nsColor: .labelColor).opacity(0.6)
}

static func dirtyIndicator(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func dirtyIndicator(for appearance: BonsplitConfiguration.Appearance) -> Color {
guard chromeBackgroundColor(for: appearance) != nil else { return dirtyIndicator }
return activeText(for: appearance).opacity(0.72)
}

static var notificationBadge: Color {
public static var notificationBadge: Color {
Color(nsColor: .systemBlue)
}

static func notificationBadge(for appearance: BonsplitConfiguration.Appearance) -> Color {
public static func notificationBadge(for appearance: BonsplitConfiguration.Appearance) -> Color {
_ = appearance
return notificationBadge
}

// MARK: - Shadows

static var tabShadow: Color {
public static var tabShadow: Color {
Color.black.opacity(0.08)
}
}
Expand Down
56 changes: 28 additions & 28 deletions Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
import Foundation

/// Sizing and spacing constants for the tab bar (following macOS HIG)
enum TabBarMetrics {
public enum TabBarMetrics {
// MARK: - Tab Bar

static let barHeight: CGFloat = 30
static let barPadding: CGFloat = 0
public static let barHeight: CGFloat = 30
public static let barPadding: CGFloat = 0

// MARK: - Individual Tabs

static let tabHeight: CGFloat = 30
static let tabMinWidth: CGFloat = 48
static let tabMaxWidth: CGFloat = 220
static let tabCornerRadius: CGFloat = 0
static let tabHorizontalPadding: CGFloat = 6
static let tabSpacing: CGFloat = 0
static let activeIndicatorHeight: CGFloat = 2
public static let tabHeight: CGFloat = 30
public static let tabMinWidth: CGFloat = 48
public static let tabMaxWidth: CGFloat = 220
public static let tabCornerRadius: CGFloat = 0
public static let tabHorizontalPadding: CGFloat = 6
public static let tabSpacing: CGFloat = 0
public static let activeIndicatorHeight: CGFloat = 2

// MARK: - Tab Content

static let iconSize: CGFloat = 14
static let titleFontSize: CGFloat = 11
static let closeButtonSize: CGFloat = 16
static let closeIconSize: CGFloat = 9
static let dirtyIndicatorSize: CGFloat = 8
static let notificationBadgeSize: CGFloat = 6
static let contentSpacing: CGFloat = 6
public static let iconSize: CGFloat = 14
public static let titleFontSize: CGFloat = 11
public static let closeButtonSize: CGFloat = 16
public static let closeIconSize: CGFloat = 9
public static let dirtyIndicatorSize: CGFloat = 8
public static let notificationBadgeSize: CGFloat = 6
public static let contentSpacing: CGFloat = 6

// MARK: - Drop Indicator

static let dropIndicatorWidth: CGFloat = 2
static let dropIndicatorHeight: CGFloat = 20
public static let dropIndicatorWidth: CGFloat = 2
public static let dropIndicatorHeight: CGFloat = 20

// MARK: - Split View

static let minimumPaneWidth: CGFloat = 100
static let minimumPaneHeight: CGFloat = 100
static let dividerThickness: CGFloat = 1
public static let minimumPaneWidth: CGFloat = 100
public static let minimumPaneHeight: CGFloat = 100
public static let dividerThickness: CGFloat = 1

// MARK: - Animations

static let selectionDuration: Double = 0.15
static let closeDuration: Double = 0.2
static let reorderDuration: Double = 0.3
static let reorderBounce: Double = 0.15
static let hoverDuration: Double = 0.1
public static let selectionDuration: Double = 0.15
public static let closeDuration: Double = 0.2
public static let reorderDuration: Double = 0.3
public static let reorderBounce: Double = 0.15
public static let hoverDuration: Double = 0.1

// MARK: - Split Animations (120fps via CADisplayLink)

/// Duration for split entry animation (fast and snappy like Hyprland)
static let splitAnimationDuration: Double = 0.15
public static let splitAnimationDuration: Double = 0.15
}
35 changes: 19 additions & 16 deletions Sources/Bonsplit/Internal/Views/TabBarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -449,24 +449,27 @@ struct TabBarView: View {
@ViewBuilder
private var splitButtons: some View {
let tooltips = controller.configuration.appearance.splitButtonTooltips
let showPaneNewTabButtons = controller.configuration.appearance.showPaneNewTabButtons
HStack(spacing: 4) {
Button {
controller.requestNewTab(kind: "terminal", inPane: pane.id)
} label: {
Image(systemName: "terminal")
.font(.system(size: 12))
}
.buttonStyle(SplitActionButtonStyle(appearance: appearance))
.safeHelp(tooltips.newTerminal)

Button {
controller.requestNewTab(kind: "browser", inPane: pane.id)
} label: {
Image(systemName: "globe")
.font(.system(size: 12))
if showPaneNewTabButtons {
Button {
controller.requestNewTab(kind: "terminal", inPane: pane.id)
} label: {
Image(systemName: "terminal")
.font(.system(size: 12))
}
.buttonStyle(SplitActionButtonStyle(appearance: appearance))
.safeHelp(tooltips.newTerminal)

Button {
controller.requestNewTab(kind: "browser", inPane: pane.id)
} label: {
Image(systemName: "globe")
.font(.system(size: 12))
}
.buttonStyle(SplitActionButtonStyle(appearance: appearance))
.safeHelp(tooltips.newBrowser)
}
.buttonStyle(SplitActionButtonStyle(appearance: appearance))
.safeHelp(tooltips.newBrowser)

Button {
// 120fps animation handled by SplitAnimator
Expand Down
30 changes: 26 additions & 4 deletions Sources/Bonsplit/Internal/Views/TabItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,16 @@ struct TabItemView: View {

@ViewBuilder
private var contextMenuContent: some View {
contextButton("Rename Tab…", action: .rename)
contextButton(
String(localized: "command.renameTab.title", defaultValue: "Rename Tab…", bundle: .main),
action: .rename
)

if contextMenuState.hasCustomTitle {
contextButton("Remove Custom Tab Name", action: .clearName)
contextButton(
String(localized: "command.clearTabName.title", defaultValue: "Clear Tab Name", bundle: .main),
action: .clearName
)
}

Divider()
Expand Down Expand Up @@ -360,9 +366,25 @@ struct TabItemView: View {

Divider()

contextButton("New Terminal Tab to Right", action: .newTerminalToRight)
contextButton(
String(localized: "menu.view.splitRight", defaultValue: "Split Right", bundle: .main),
action: .splitRight
)

contextButton(
String(localized: "menu.view.splitDown", defaultValue: "Split Down", bundle: .main),
action: .splitDown
)

contextButton("New Browser Tab to Right", action: .newBrowserToRight)
contextButton(
String(localized: "menu.view.splitBrowserRight", defaultValue: "Split Browser Right", bundle: .main),
action: .splitBrowserRight
)

contextButton(
String(localized: "menu.view.splitBrowserDown", defaultValue: "Split Browser Down", bundle: .main),
action: .splitBrowserDown
)

if contextMenuState.isBrowser {
Divider()
Expand Down
5 changes: 5 additions & 0 deletions Sources/Bonsplit/Public/BonsplitConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ extension BonsplitConfiguration {
/// Whether to show split buttons in the tab bar
public var showSplitButtons: Bool

/// Whether to show pane-level new-tab buttons in the tab bar
public var showPaneNewTabButtons: Bool

/// Tooltip text for the tab bar's right-side action buttons
public var splitButtonTooltips: SplitButtonTooltips

Expand Down Expand Up @@ -212,6 +215,7 @@ extension BonsplitConfiguration {
minimumPaneWidth: CGFloat = 100,
minimumPaneHeight: CGFloat = 100,
showSplitButtons: Bool = true,
showPaneNewTabButtons: Bool = true,
splitButtonTooltips: SplitButtonTooltips = .default,
animationDuration: Double = 0.15,
enableAnimations: Bool = true,
Expand All @@ -224,6 +228,7 @@ extension BonsplitConfiguration {
self.minimumPaneWidth = minimumPaneWidth
self.minimumPaneHeight = minimumPaneHeight
self.showSplitButtons = showSplitButtons
self.showPaneNewTabButtons = showPaneNewTabButtons
self.splitButtonTooltips = splitButtonTooltips
self.animationDuration = animationDuration
self.enableAnimations = enableAnimations
Expand Down
4 changes: 4 additions & 0 deletions Sources/Bonsplit/Public/Types/TabContextAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public enum TabContextAction: String, CaseIterable, Sendable {
case moveToRightPane
case newTerminalToRight
case newBrowserToRight
case splitRight
case splitDown
case splitBrowserRight
case splitBrowserDown
case reload
case duplicate
case togglePin
Expand Down