diff --git a/Sources/Bonsplit/Internal/Views/TabDragPreview.swift b/Sources/Bonsplit/Internal/Views/TabDragPreview.swift index 7987397..075f65c 100644 --- a/Sources/Bonsplit/Internal/Views/TabDragPreview.swift +++ b/Sources/Bonsplit/Internal/Views/TabDragPreview.swift @@ -14,7 +14,7 @@ struct TabDragPreview: View { } Text(tab.title) - .font(.system(size: TabBarMetrics.titleFontSize)) + .font(.system(size: appearance.tabTitleFontSize)) .lineLimit(1) .foregroundStyle(TabBarColors.activeText(for: appearance)) } diff --git a/Sources/Bonsplit/Internal/Views/TabItemView.swift b/Sources/Bonsplit/Internal/Views/TabItemView.swift index 2547211..152b86d 100644 --- a/Sources/Bonsplit/Internal/Views/TabItemView.swift +++ b/Sources/Bonsplit/Internal/Views/TabItemView.swift @@ -119,7 +119,7 @@ struct TabItemView: View { .onChange(of: tab.icon) { _ in updateGlobeFallback() } Text(tab.title) - .font(.system(size: TabBarMetrics.titleFontSize)) + .font(.system(size: appearance.tabTitleFontSize)) .lineLimit(1) .foregroundStyle( isSelected @@ -133,13 +133,13 @@ struct TabItemView: View { onZoomToggle() } label: { Image(systemName: "arrow.up.left.and.arrow.down.right") - .font(.system(size: max(8, TabBarMetrics.titleFontSize - 2), weight: .semibold)) + .font(.system(size: accessoryFontSize, weight: .semibold)) .foregroundStyle( isZoomHovered ? TabBarColors.activeText(for: appearance) : TabBarColors.inactiveText(for: appearance) ) - .frame(width: TabBarMetrics.closeButtonSize, height: TabBarMetrics.closeButtonSize) + .frame(width: accessorySlotSize, height: accessorySlotSize) .background( Circle() .fill( @@ -217,14 +217,23 @@ struct TabItemView: View { private var shortcutHintSlotWidth: CGFloat { guard let label = shortcutHintLabel else { - return TabBarMetrics.closeButtonSize + return accessorySlotSize } let positiveDebugInset = max(0, CGFloat(TabControlShortcutHintDebugSettings.clamped(controlShortcutHintXOffset))) + 2 - return max(TabBarMetrics.closeButtonSize, shortcutHintWidth(for: label) + positiveDebugInset) + return max(accessorySlotSize, shortcutHintWidth(for: label) + positiveDebugInset) + } + + private var accessoryFontSize: CGFloat { + max(8, appearance.tabTitleFontSize - 2) + } + + private var accessorySlotSize: CGFloat { + // Keep accessory affordances readable when the tab title font is increased. + min(TabBarMetrics.tabHeight, max(TabBarMetrics.closeButtonSize, ceil(accessoryFontSize + 4))) } private func shortcutHintWidth(for label: String) -> CGFloat { - let font = NSFont.systemFont(ofSize: max(8, TabBarMetrics.titleFontSize - 2), weight: .semibold) + let font = NSFont.systemFont(ofSize: accessoryFontSize, weight: .semibold) let textWidth = (label as NSString).size(withAttributes: [.font: font]).width return ceil(textWidth) + 8 } @@ -234,7 +243,7 @@ struct TabItemView: View { ZStack(alignment: .center) { if let shortcutHintLabel { Text(shortcutHintLabel) - .font(.system(size: max(8, TabBarMetrics.titleFontSize - 2), weight: .semibold, design: .rounded)) + .font(.system(size: accessoryFontSize, weight: .semibold, design: .rounded)) .monospacedDigit() .lineLimit(1) .fixedSize(horizontal: true, vertical: false) @@ -266,7 +275,7 @@ struct TabItemView: View { .opacity(showsShortcutHint ? 0 : 1) .allowsHitTesting(!showsShortcutHint) } - .frame(width: shortcutHintSlotWidth, height: TabBarMetrics.closeButtonSize, alignment: .center) + .frame(width: shortcutHintSlotWidth, height: accessorySlotSize, alignment: .center) .animation(.easeInOut(duration: 0.14), value: showsShortcutHint) } @@ -478,7 +487,7 @@ struct TabItemView: View { Image(systemName: "pin.fill") .font(.system(size: TabBarMetrics.closeIconSize, weight: .semibold)) .foregroundStyle(TabBarColors.inactiveText(for: appearance)) - .frame(width: TabBarMetrics.closeButtonSize, height: TabBarMetrics.closeButtonSize) + .frame(width: accessorySlotSize, height: accessorySlotSize) .saturation(saturation) } } else if isSelected || isHovered || isCloseHovered { @@ -493,7 +502,7 @@ struct TabItemView: View { ? TabBarColors.activeText(for: appearance) : TabBarColors.inactiveText(for: appearance) ) - .frame(width: TabBarMetrics.closeButtonSize, height: TabBarMetrics.closeButtonSize) + .frame(width: accessorySlotSize, height: accessorySlotSize) .background( Circle() .fill( @@ -510,7 +519,7 @@ struct TabItemView: View { .saturation(saturation) } } - .frame(width: TabBarMetrics.closeButtonSize, height: TabBarMetrics.closeButtonSize) + .frame(width: accessorySlotSize, height: accessorySlotSize) .animation(.easeInOut(duration: TabBarMetrics.hoverDuration), value: isHovered) .animation(.easeInOut(duration: TabBarMetrics.hoverDuration), value: isCloseHovered) } diff --git a/Sources/Bonsplit/Public/BonsplitConfiguration.swift b/Sources/Bonsplit/Public/BonsplitConfiguration.swift index c852b3f..6b7d97c 100644 --- a/Sources/Bonsplit/Public/BonsplitConfiguration.swift +++ b/Sources/Bonsplit/Public/BonsplitConfiguration.swift @@ -155,6 +155,9 @@ extension BonsplitConfiguration { /// Maximum width of a tab public var tabMaxWidth: CGFloat + /// Font size for tab titles in the surface tab bar + public var tabTitleFontSize: CGFloat + /// Spacing between tabs public var tabSpacing: CGFloat @@ -198,13 +201,15 @@ extension BonsplitConfiguration { public static let compact = Appearance( tabBarHeight: 28, tabMinWidth: 100, - tabMaxWidth: 160 + tabMaxWidth: 160, + tabTitleFontSize: 11 ) public static let spacious = Appearance( tabBarHeight: 38, tabMinWidth: 160, tabMaxWidth: 280, + tabTitleFontSize: 11, tabSpacing: 2 ) @@ -214,6 +219,7 @@ extension BonsplitConfiguration { tabBarHeight: CGFloat = 33, tabMinWidth: CGFloat = 140, tabMaxWidth: CGFloat = 220, + tabTitleFontSize: CGFloat = 11, tabSpacing: CGFloat = 0, minimumPaneWidth: CGFloat = 100, minimumPaneHeight: CGFloat = 100, @@ -228,6 +234,7 @@ extension BonsplitConfiguration { self.tabBarHeight = tabBarHeight self.tabMinWidth = tabMinWidth self.tabMaxWidth = tabMaxWidth + self.tabTitleFontSize = tabTitleFontSize self.tabSpacing = tabSpacing self.minimumPaneWidth = minimumPaneWidth self.minimumPaneHeight = minimumPaneHeight