From 2a7565bdd06f640a418cdd80c4e10718a5a3cda9 Mon Sep 17 00:00:00 2001 From: DreaminDani Date: Fri, 13 Mar 2026 11:10:12 -0600 Subject: [PATCH] Add bonsplitUIScale environment key for host app UI scaling Allows host apps to inject a scale factor that Bonsplit tab bar views use to multiply font sizes, enabling configurable UI scaling. Co-Authored-By: Claude Opus 4.6 --- Sources/Bonsplit/Internal/Views/TabBarView.swift | 11 ++++++----- .../Bonsplit/Internal/Views/TabDragPreview.swift | 5 +++-- Sources/Bonsplit/Internal/Views/TabItemView.swift | 13 +++++++------ Sources/Bonsplit/Public/UIScaleEnvironment.swift | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 Sources/Bonsplit/Public/UIScaleEnvironment.swift diff --git a/Sources/Bonsplit/Internal/Views/TabBarView.swift b/Sources/Bonsplit/Internal/Views/TabBarView.swift index e14b6e96..9672f9d7 100644 --- a/Sources/Bonsplit/Internal/Views/TabBarView.swift +++ b/Sources/Bonsplit/Internal/Views/TabBarView.swift @@ -57,7 +57,8 @@ struct TabContextMenuState { struct TabBarView: View { @Environment(BonsplitController.self) private var controller @Environment(SplitViewController.self) private var splitViewController - + @Environment(\.bonsplitUIScale) private var uiScale + @Bindable var pane: PaneState let isFocused: Bool var showSplitButtons: Bool = true @@ -448,7 +449,7 @@ struct TabBarView: View { controller.requestNewTab(kind: "terminal", inPane: pane.id) } label: { Image(systemName: "terminal") - .font(.system(size: 12)) + .font(.system(size: 12 * uiScale)) } .buttonStyle(SplitActionButtonStyle(appearance: appearance)) .safeHelp(tooltips.newTerminal) @@ -457,7 +458,7 @@ struct TabBarView: View { controller.requestNewTab(kind: "browser", inPane: pane.id) } label: { Image(systemName: "globe") - .font(.system(size: 12)) + .font(.system(size: 12 * uiScale)) } .buttonStyle(SplitActionButtonStyle(appearance: appearance)) .safeHelp(tooltips.newBrowser) @@ -467,7 +468,7 @@ struct TabBarView: View { controller.splitPane(pane.id, orientation: .horizontal) } label: { Image(systemName: "square.split.2x1") - .font(.system(size: 12)) + .font(.system(size: 12 * uiScale)) } .buttonStyle(SplitActionButtonStyle(appearance: appearance)) .safeHelp(tooltips.splitRight) @@ -477,7 +478,7 @@ struct TabBarView: View { controller.splitPane(pane.id, orientation: .vertical) } label: { Image(systemName: "square.split.1x2") - .font(.system(size: 12)) + .font(.system(size: 12 * uiScale)) } .buttonStyle(SplitActionButtonStyle(appearance: appearance)) .safeHelp(tooltips.splitDown) diff --git a/Sources/Bonsplit/Internal/Views/TabDragPreview.swift b/Sources/Bonsplit/Internal/Views/TabDragPreview.swift index 7987397f..e93ec282 100644 --- a/Sources/Bonsplit/Internal/Views/TabDragPreview.swift +++ b/Sources/Bonsplit/Internal/Views/TabDragPreview.swift @@ -4,17 +4,18 @@ import SwiftUI struct TabDragPreview: View { let tab: TabItem let appearance: BonsplitConfiguration.Appearance + @Environment(\.bonsplitUIScale) private var uiScale var body: some View { HStack(spacing: TabBarMetrics.contentSpacing) { if let iconName = tab.icon { Image(systemName: iconName) - .font(.system(size: TabBarMetrics.iconSize)) + .font(.system(size: TabBarMetrics.iconSize * uiScale)) .foregroundStyle(TabBarColors.activeText(for: appearance)) } Text(tab.title) - .font(.system(size: TabBarMetrics.titleFontSize)) + .font(.system(size: TabBarMetrics.titleFontSize * uiScale)) .lineLimit(1) .foregroundStyle(TabBarColors.activeText(for: appearance)) } diff --git a/Sources/Bonsplit/Internal/Views/TabItemView.swift b/Sources/Bonsplit/Internal/Views/TabItemView.swift index bc23a339..d6c03fdf 100644 --- a/Sources/Bonsplit/Internal/Views/TabItemView.swift +++ b/Sources/Bonsplit/Internal/Views/TabItemView.swift @@ -63,6 +63,7 @@ struct TabItemView: View { @AppStorage(TabControlShortcutHintDebugSettings.xKey) private var controlShortcutHintXOffset = TabControlShortcutHintDebugSettings.defaultX @AppStorage(TabControlShortcutHintDebugSettings.yKey) private var controlShortcutHintYOffset = TabControlShortcutHintDebugSettings.defaultY @AppStorage(TabControlShortcutHintDebugSettings.alwaysShowKey) private var alwaysShowShortcutHints = TabControlShortcutHintDebugSettings.defaultAlwaysShow + @Environment(\.bonsplitUIScale) private var uiScale var body: some View { HStack(spacing: 0) { @@ -91,7 +92,7 @@ struct TabItemView: View { .stroke(iconTint.opacity(0.25), lineWidth: 1) } else { Image(systemName: iconName) - .font(.system(size: glyphSize(for: iconName))) + .font(.system(size: glyphSize(for: iconName) * uiScale)) .foregroundStyle(iconTint) } } @@ -119,7 +120,7 @@ struct TabItemView: View { .onChange(of: tab.icon) { _ in updateGlobeFallback() } Text(tab.title) - .font(.system(size: TabBarMetrics.titleFontSize)) + .font(.system(size: TabBarMetrics.titleFontSize * uiScale)) .lineLimit(1) .foregroundStyle( isSelected @@ -133,7 +134,7 @@ 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: max(8, (TabBarMetrics.titleFontSize - 2) * uiScale), weight: .semibold)) .foregroundStyle( isZoomHovered ? TabBarColors.activeText(for: appearance) @@ -234,7 +235,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: max(8, (TabBarMetrics.titleFontSize - 2) * uiScale), weight: .semibold, design: .rounded)) .monospacedDigit() .lineLimit(1) .fixedSize(horizontal: true, vertical: false) @@ -448,7 +449,7 @@ struct TabItemView: View { if tab.isPinned { if isSelected || isHovered || isCloseHovered || (!tab.isDirty && !tab.showsNotificationBadge) { Image(systemName: "pin.fill") - .font(.system(size: TabBarMetrics.closeIconSize, weight: .semibold)) + .font(.system(size: TabBarMetrics.closeIconSize * uiScale, weight: .semibold)) .foregroundStyle(TabBarColors.inactiveText(for: appearance)) .frame(width: TabBarMetrics.closeButtonSize, height: TabBarMetrics.closeButtonSize) .saturation(saturation) @@ -459,7 +460,7 @@ struct TabItemView: View { onClose() } label: { Image(systemName: "xmark") - .font(.system(size: TabBarMetrics.closeIconSize, weight: .semibold)) + .font(.system(size: TabBarMetrics.closeIconSize * uiScale, weight: .semibold)) .foregroundStyle( isCloseHovered ? TabBarColors.activeText(for: appearance) diff --git a/Sources/Bonsplit/Public/UIScaleEnvironment.swift b/Sources/Bonsplit/Public/UIScaleEnvironment.swift new file mode 100644 index 00000000..5f00f5d5 --- /dev/null +++ b/Sources/Bonsplit/Public/UIScaleEnvironment.swift @@ -0,0 +1,15 @@ +import SwiftUI + +/// Environment key for UI scale factor. Host apps inject a value; Bonsplit views +/// multiply font sizes by this factor so the tab bar respects the app-wide scale. +private struct BonsplitUIScaleKey: EnvironmentKey { + static let defaultValue: CGFloat = 1.0 +} + +public extension EnvironmentValues { + /// UI scale factor applied to Bonsplit font sizes. Defaults to 1.0. + var bonsplitUIScale: CGFloat { + get { self[BonsplitUIScaleKey.self] } + set { self[BonsplitUIScaleKey.self] = newValue } + } +}