Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
91 changes: 3 additions & 88 deletions Sources/Bonsplit/Internal/Views/TabBarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,6 @@ enum TabBarStyling {
static let splitButtonsSpacing: CGFloat = 4
static let splitButtonsLeadingPadding: CGFloat = 6
static let splitButtonsTrailingPadding: CGFloat = 8
/// Below this width the individual action buttons consume the tab lane.
static let narrowPaneThreshold: CGFloat = 520
/// Width reserved by the single overflow button in a narrow pane.
static let collapsedActionLaneWidth: CGFloat = 36

static var splitButtonsBackdropWidth: CGFloat {
splitButtonsBackdropWidth(buttonCount: BonsplitConfiguration.SplitActionButton.defaults.count)
Expand All @@ -194,18 +190,6 @@ enum TabBarStyling {
+ (CGFloat(max(0, buttonCount - 1)) * splitButtonsSpacing)
}

static func isNarrowPane(width: CGFloat) -> Bool {
width > 0 && width < narrowPaneThreshold
}

static func splitActionButtonCount(
isNarrowPane: Bool,
shouldShowSplitButtons: Bool,
visibleButtonCount: Int
) -> Int {
isNarrowPane && shouldShowSplitButtons ? 1 : max(0, visibleButtonCount)
}

static func minimumVisibleSplitButtonLaneWidth(buttonCount: Int) -> CGFloat {
splitButtonsBackdropWidth(
buttonCount: min(max(0, buttonCount), minimumFullyVisibleSplitButtonCount)
Expand Down Expand Up @@ -960,16 +944,11 @@ struct TabBarView: View {
}

private var tabBarLayout: TabBarLayout {
let actionButtonCount = TabBarStyling.splitActionButtonCount(
isNarrowPane: isNarrowPane,
shouldShowSplitButtons: shouldShowSplitButtons,
visibleButtonCount: visibleSplitButtons.count
)
return TabBarLayout(
TabBarLayout(
tabBarHeight: appearance.tabBarHeight,
availableWidth: containerWidth,
tabContentWidthExcludingSplitButtonLane: tabContentWidthExcludingSplitButtonLane,
splitButtonCount: actionButtonCount,
splitButtonCount: visibleSplitButtons.count,
splitButtonLaneVisible: shouldShowSplitButtons,
reservesSplitButtonLane: splitButtonLane.reservesLane,
measuredSplitButtonLaneWidth: measuredSplitButtonLaneWidth
Expand All @@ -991,10 +970,6 @@ struct TabBarView: View {
return pane.splitButtonsOverride ?? appearance.splitButtons
}

private var isNarrowPane: Bool {
TabBarStyling.isNarrowPane(width: containerWidth)
}

private var shouldRenderSplitButtons: Bool {
!visibleSplitButtons.isEmpty
}
Expand Down Expand Up @@ -1526,13 +1501,7 @@ struct TabBarView: View {
@ViewBuilder
private var splitButtonChrome: some View {
if shouldRenderSplitButtons {
Group {
if isNarrowPane {
collapsedSplitButtonMenu
} else {
splitButtons
}
}
splitButtons
.frame(width: splitButtonsBackdropWidth, height: tabBarHeight, alignment: .trailing)
.mask {
Rectangle()
Expand All @@ -1547,60 +1516,6 @@ struct TabBarView: View {
}
}

@ViewBuilder
private var collapsedSplitButtonMenu: some View {
Menu {
ForEach(visibleSplitButtons) { button in
let title = splitActionButtonTooltip(button, tooltips: appearance.splitButtonTooltips)
if button.menuBehavior == .primary {
Button {
presentSplitActionMenu(button)
} label: {
Text(title + "\u{2026}")
}
} else {
Button {
performSplitActionButton(button)
} label: {
Text(title)
}
if let alternate = collapsedAlternateTitle(button) {
Button {
performSplitActionButton(button, optionKeyHeld: true)
} label: {
Text(alternate)
}
}
if button.menuBehavior == .secondary {
Button {
presentSplitActionMenu(button)
} label: {
Text(title + "\u{2026}")
}
}
}
}
} label: {
Image(systemName: "ellipsis")
.font(.system(size: 13, weight: .semibold))
.frame(width: TabBarStyling.collapsedActionLaneWidth, height: tabBarHeight)
}
.menuStyle(.borderlessButton)
.buttonStyle(.plain)
.accessibilityLabel(String(localized: "tabBar.moreActions", defaultValue: "More Tab Actions"))
.accessibilityIdentifier("paneTabBarControl.moreActions")
}

/// Title of a button's Option-click action in the collapsed overflow menu,
/// nil when it has none or it matches the click action.
private func collapsedAlternateTitle(_ button: BonsplitConfiguration.SplitActionButton) -> String? {
guard let alternateAction = button.alternateAction, alternateAction != button.action else { return nil }
var alternate = button
alternate.action = alternateAction
alternate.tooltip = nil
return splitActionButtonTooltip(alternate, tooltips: appearance.splitButtonTooltips)
}

@ViewBuilder
private var splitButtonBackdropChrome: some View {
let snapshot = chromeSnapshot
Expand Down
14 changes: 4 additions & 10 deletions Tests/BonsplitTests/BonsplitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5540,11 +5540,9 @@ final class BonsplitTests: XCTestCase {
}
}

/// A tab bar wide enough to show the full action lane. Below
/// TabBarStyling.narrowPaneThreshold the tab bar collapses its actions into
/// one overflow button, so a test that samples the full lane's chrome must
/// render at least this wide.
private static let fullActionLaneWidth: CGFloat = TabBarStyling.narrowPaneThreshold + 80
/// A tab bar wide enough that the selected tab and the full action lane
/// both fit, so a test can sample the lane's chrome.
private static let fullActionLaneWidth: CGFloat = 600

/// A selected-tab title long enough that its tab, capped at the bar's
/// width, reaches under the full action lane.
Expand All @@ -5554,11 +5552,7 @@ final class BonsplitTests: XCTestCase {
)

private func visibleSplitButtonLaneWidth(size: NSSize, buttonCount: Int) -> CGFloat {
XCTAssertFalse(
TabBarStyling.isNarrowPane(width: size.width),
"A \(size.width)pt tab bar collapses its actions into one button; render at least fullActionLaneWidth to sample the full lane."
)
return TabBarLayout(
TabBarLayout(
tabBarHeight: size.height,
availableWidth: size.width,
splitButtonCount: buttonCount,
Expand Down
32 changes: 0 additions & 32 deletions Tests/BonsplitTests/TabBarLayoutFeedbackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,6 @@ import XCTest
#if DEBUG
@MainActor
final class TabBarLayoutFeedbackTests: XCTestCase {
func testNarrowPanesCollapseActionLaneBeforeTabsLoseSpace() {
XCTAssertTrue(TabBarStyling.isNarrowPane(width: 400))
XCTAssertFalse(TabBarStyling.isNarrowPane(width: 520))
XCTAssertEqual(
TabBarStyling.splitButtonsBackdropWidth(buttonCount: 1),
TabBarStyling.collapsedActionLaneWidth
)
XCTAssertGreaterThan(
TabBarStyling.splitButtonsBackdropWidth(buttonCount: 7),
TabBarStyling.collapsedActionLaneWidth
)
}

func testNarrowPaneDoesNotReserveActionLaneWhenActionsAreHidden() {
XCTAssertEqual(
TabBarStyling.splitActionButtonCount(
isNarrowPane: true,
shouldShowSplitButtons: false,
visibleButtonCount: 0
),
0
)
XCTAssertEqual(
TabBarStyling.splitActionButtonCount(
isNarrowPane: true,
shouldShowSplitButtons: true,
visibleButtonCount: 7
),
1
)
}

func testPinnedTabStaysAtLeadingEdgeWhileUnpinnedTabsScroll() throws {
let size = NSSize(width: 420, height: TabBarMetrics.barHeight)
let controller = BonsplitController(
Expand Down
Loading