diff --git a/Sources/Bonsplit/Internal/Styling/TabBarColors.swift b/Sources/Bonsplit/Internal/Styling/TabBarColors.swift index f86a5fe9..1686392e 100644 --- a/Sources/Bonsplit/Internal/Styling/TabBarColors.swift +++ b/Sources/Bonsplit/Internal/Styling/TabBarColors.swift @@ -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 @@ -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 } @@ -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 } @@ -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 { @@ -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 } @@ -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) } } diff --git a/Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift b/Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift index 5e7d6573..c3659602 100644 --- a/Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift +++ b/Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift @@ -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 } diff --git a/Sources/Bonsplit/Internal/Views/TabBarView.swift b/Sources/Bonsplit/Internal/Views/TabBarView.swift index 9663aad0..f3009672 100644 --- a/Sources/Bonsplit/Internal/Views/TabBarView.swift +++ b/Sources/Bonsplit/Internal/Views/TabBarView.swift @@ -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 diff --git a/Sources/Bonsplit/Internal/Views/TabItemView.swift b/Sources/Bonsplit/Internal/Views/TabItemView.swift index 25472115..78aacab3 100644 --- a/Sources/Bonsplit/Internal/Views/TabItemView.swift +++ b/Sources/Bonsplit/Internal/Views/TabItemView.swift @@ -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: .module), + action: .rename + ) if contextMenuState.hasCustomTitle { - contextButton("Remove Custom Tab Name", action: .clearName) + contextButton( + String(localized: "command.clearTabName.title", defaultValue: "Clear Tab Name", bundle: .module), + action: .clearName + ) } Divider() @@ -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: .module), + action: .splitRight + ) + + contextButton( + String(localized: "menu.view.splitDown", defaultValue: "Split Down", bundle: .module), + action: .splitDown + ) - contextButton("New Browser Tab to Right", action: .newBrowserToRight) + contextButton( + String(localized: "menu.view.splitBrowserRight", defaultValue: "Split Browser Right", bundle: .module), + action: .splitBrowserRight + ) + + contextButton( + String(localized: "menu.view.splitBrowserDown", defaultValue: "Split Browser Down", bundle: .module), + action: .splitBrowserDown + ) if contextMenuState.isBrowser { Divider() diff --git a/Sources/Bonsplit/Public/BonsplitConfiguration.swift b/Sources/Bonsplit/Public/BonsplitConfiguration.swift index e8c77b49..3e6f2f99 100644 --- a/Sources/Bonsplit/Public/BonsplitConfiguration.swift +++ b/Sources/Bonsplit/Public/BonsplitConfiguration.swift @@ -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 @@ -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, @@ -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 diff --git a/Sources/Bonsplit/Public/Types/TabContextAction.swift b/Sources/Bonsplit/Public/Types/TabContextAction.swift index 47ec09d3..7335aea8 100644 --- a/Sources/Bonsplit/Public/Types/TabContextAction.swift +++ b/Sources/Bonsplit/Public/Types/TabContextAction.swift @@ -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