Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
A5001500 /* CmuxWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001510 /* CmuxWebView.swift */; };
A5001501 /* UITestRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001511 /* UITestRecorder.swift */; };
A5001226 /* SocketControlSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001225 /* SocketControlSettings.swift */; };
A500AA01 /* UIScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = A500AA00 /* UIScale.swift */; };
A5001601 /* SentryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001600 /* SentryHelper.swift */; };
A5001621 /* AppleScriptSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001620 /* AppleScriptSupport.swift */; };
D1320AA0D1320AA0D1320AA1 /* AppIconDockTilePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1320AA0D1320AA0D1320AA4 /* AppIconDockTilePlugin.swift */; };
Expand Down Expand Up @@ -231,6 +232,7 @@
A5001511 /* UITestRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestRecorder.swift; sourceTree = "<group>"; };
A5001520 /* PostHogAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalytics.swift; sourceTree = "<group>"; };
A5001225 /* SocketControlSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketControlSettings.swift; sourceTree = "<group>"; };
A500AA00 /* UIScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScale.swift; sourceTree = "<group>"; };
A5001410 /* Panel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/Panel.swift; sourceTree = "<group>"; };
A5001411 /* TerminalPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/TerminalPanel.swift; sourceTree = "<group>"; };
A5001412 /* BrowserPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/BrowserPanel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -478,6 +480,7 @@
A5001544 /* TerminalImageTransfer.swift */,
A5001545 /* TerminalSSHSessionDetector.swift */,
A5001225 /* SocketControlSettings.swift */,
A500AA00 /* UIScale.swift */,
A5001600 /* SentryHelper.swift */,
A5001620 /* AppleScriptSupport.swift */,
D1320AA0D1320AA0D1320AA4 /* AppIconDockTilePlugin.swift */,
Expand Down Expand Up @@ -804,6 +807,7 @@
A5001542 /* TerminalImageTransfer.swift in Sources */,
A5001543 /* TerminalSSHSessionDetector.swift in Sources */,
A5001226 /* SocketControlSettings.swift in Sources */,
A500AA01 /* UIScale.swift in Sources */,
A5001601 /* SentryHelper.swift in Sources */,
A5001621 /* AppleScriptSupport.swift in Sources */,
A5001093 /* AppDelegate.swift in Sources */,
Expand Down
51 changes: 51 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -47782,6 +47782,57 @@
}
}
},
"settings.app.uiScaleFactor": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "UI Scale Factor"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "UIスケール係数"
}
}
}
},
"settings.app.uiScaleFactor.subtitle": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Scale fonts and UI elements. Does not affect terminal text."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "フォントとUI要素を拡大縮小します。ターミナルのテキストには影響しません。"
}
}
}
},
"settings.app.uiScaleFactor.reset": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reset to default"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "デフォルトに戻す"
}
}
}
},
"settings.app.newWorkspacePlacement": {
"extractionState": "manual",
"localizations": {
Expand Down
99 changes: 51 additions & 48 deletions Sources/ContentView.swift

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Sources/NotificationsPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ struct NotificationsPage: View {
private var emptyState: some View {
VStack(spacing: 8) {
Image(systemName: "bell.slash")
.font(.system(size: 32))
.scaledFont(size: 32)
.foregroundColor(.secondary)
Text(String(localized: "notifications.empty.title", defaultValue: "No notifications yet"))
.font(.headline)
Expand Down Expand Up @@ -160,7 +160,7 @@ struct ShortcutAnnotation: View {

private var badge: some View {
Text(text)
.font(.system(size: 10, weight: .semibold, design: .rounded))
.scaledFont(size: 10, weight: .semibold, design: .rounded)
.foregroundStyle(.primary)
.padding(.horizontal, 6)
.padding(.vertical, 2)
Expand Down
22 changes: 11 additions & 11 deletions Sources/Panels/BrowserPanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,7 @@ struct BrowserPanelView: View {
panel.goBack()
}) {
Image(systemName: "chevron.left")
.font(.system(size: 12, weight: .medium))
.scaledFont(size: 12, weight: .medium)
.frame(width: addressBarButtonHitSize, height: addressBarButtonHitSize, alignment: .center)
.contentShape(Rectangle())
}
Expand All @@ -895,7 +895,7 @@ struct BrowserPanelView: View {
panel.goForward()
}) {
Image(systemName: "chevron.right")
.font(.system(size: 12, weight: .medium))
.scaledFont(size: 12, weight: .medium)
.frame(width: addressBarButtonHitSize, height: addressBarButtonHitSize, alignment: .center)
.contentShape(Rectangle())
}
Expand All @@ -918,7 +918,7 @@ struct BrowserPanelView: View {
}
}) {
Image(systemName: panel.isLoading ? "xmark" : "arrow.clockwise")
.font(.system(size: 12, weight: .medium))
.scaledFont(size: 12, weight: .medium)
.frame(width: addressBarButtonHitSize, height: addressBarButtonHitSize, alignment: .center)
.contentShape(Rectangle())
}
Expand All @@ -930,7 +930,7 @@ struct BrowserPanelView: View {
ProgressView()
.controlSize(.small)
Text(String(localized: "browser.downloading", defaultValue: "Downloading..."))
.font(.system(size: 11))
.scaledFont(size: 11)
.foregroundStyle(.secondary)
}
.padding(.leading, 6)
Expand Down Expand Up @@ -964,7 +964,7 @@ struct BrowserPanelView: View {
Image(systemName: devToolsIconOption.rawValue)
.symbolRenderingMode(.monochrome)
.cmuxFlatSymbolColorRendering()
.font(.system(size: devToolsButtonIconSize, weight: .medium))
.scaledFont(size: devToolsButtonIconSize, weight: .medium)
.foregroundStyle(devToolsColorOption.color)
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
}
Expand Down Expand Up @@ -1009,7 +1009,7 @@ struct BrowserPanelView: View {
Image(systemName: browserThemeMode.iconName)
.symbolRenderingMode(.monochrome)
.cmuxFlatSymbolColorRendering()
.font(.system(size: devToolsButtonIconSize, weight: .medium))
.scaledFont(size: devToolsButtonIconSize, weight: .medium)
.foregroundStyle(browserThemeModeIconColor)
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
}
Expand Down Expand Up @@ -1129,11 +1129,11 @@ struct BrowserPanelView: View {
} label: {
HStack(spacing: 8) {
Image(systemName: mode == browserThemeMode ? "checkmark" : "circle")
.font(.system(size: 10, weight: .semibold))
.scaledFont(size: 10, weight: .semibold)
.opacity(mode == browserThemeMode ? 1.0 : 0.0)
.frame(width: 12, alignment: .center)
Text(mode.displayName)
.font(.system(size: 12))
.scaledFont(size: 12)
Spacer(minLength: 0)
}
.padding(.horizontal, 8)
Expand Down Expand Up @@ -1162,7 +1162,7 @@ struct BrowserPanelView: View {
return HStack(spacing: 4) {
if showSecureBadge {
Image(systemName: "lock.fill")
.font(.system(size: 10))
.scaledFont(size: 10)
.foregroundColor(.secondary)
}

Expand Down Expand Up @@ -4275,13 +4275,13 @@ private struct OmnibarSuggestionsView: View {
} label: {
HStack(spacing: 6) {
Text(item.listText)
.font(.system(size: 11))
.scaledFont(size: 11)
.foregroundStyle(listTextColor)
.lineLimit(1)
.truncationMode(.tail)
if let badge = item.trailingBadgeText {
Text(badge)
.font(.system(size: 9.5, weight: .medium))
.scaledFont(size: 9.5, weight: .medium)
.foregroundStyle(badgeTextColor)
.padding(.horizontal, 6)
.padding(.vertical, 2)
Expand Down
8 changes: 4 additions & 4 deletions Sources/Panels/MarkdownPanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ struct MarkdownPanelView: View {
HStack(spacing: 6) {
Image(systemName: "doc.richtext")
.foregroundColor(.secondary)
.font(.system(size: 12))
.scaledFont(size: 12)
Text(panel.filePath)
.font(.system(size: 11, design: .monospaced))
.scaledFont(size: 11, design: .monospaced)
.foregroundColor(.secondary)
.lineLimit(1)
.truncationMode(.middle)
Expand All @@ -84,13 +84,13 @@ struct MarkdownPanelView: View {
private var fileUnavailableView: some View {
VStack(spacing: 12) {
Image(systemName: "doc.questionmark")
.font(.system(size: 40))
.scaledFont(size: 40)
.foregroundColor(.secondary)
Text(String(localized: "markdown.fileUnavailable.title", defaultValue: "File unavailable"))
.font(.headline)
.foregroundColor(.primary)
Text(panel.filePath)
.font(.system(size: 12, design: .monospaced))
.scaledFont(size: 12, design: .monospaced)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.textSelection(.enabled)
Expand Down
88 changes: 88 additions & 0 deletions Sources/UIScale.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import Bonsplit
import SwiftUI

// MARK: - UI Scale Settings

enum UIScaleSettings {
static let key = "uiScaleFactor"
static let defaultValue: Double = 1.0
static let minimumValue: Double = 0.5
static let maximumValue: Double = 3.0
static let stepIncrement: Double = 0.25

static func resolvedScale(defaults: UserDefaults = .standard) -> CGFloat {
let value = defaults.object(forKey: key) as? Double ?? defaultValue
return CGFloat(clampedValue(value))
}

static func clampedValue(_ value: Double) -> Double {
min(maximumValue, max(minimumValue, value))
}
}

// MARK: - Environment Key

private struct UIScaleKey: EnvironmentKey {
static let defaultValue: CGFloat = 1.0
}

extension EnvironmentValues {
var uiScale: CGFloat {
get { self[UIScaleKey.self] }
set { self[UIScaleKey.self] = newValue }
}
}

// MARK: - Scaled Font View Modifier

private struct ScaledFontModifier: ViewModifier {
@Environment(\.uiScale) private var uiScale
let size: CGFloat
let weight: Font.Weight
let design: Font.Design?

func body(content: Content) -> some View {
let scaledSize = size * uiScale
if let design {
content.font(.system(size: scaledSize, weight: weight, design: design))
} else {
content.font(.system(size: scaledSize, weight: weight))
}
}
}

extension View {
func scaledFont(size: CGFloat, weight: Font.Weight = .regular, design: Font.Design? = nil) -> some View {
modifier(ScaledFontModifier(size: size, weight: weight, design: design))
}
}

// MARK: - Environment Injection Modifier

/// ViewModifier that owns an @AppStorage and reactively injects the uiScale environment.
/// Use this instead of injecting from App.body, which doesn't re-evaluate on UserDefaults changes.
private struct UIScaleEnvironmentModifier: ViewModifier {
@AppStorage(UIScaleSettings.key) private var scaleFactor = UIScaleSettings.defaultValue

func body(content: Content) -> some View {
let scale = CGFloat(UIScaleSettings.clampedValue(scaleFactor))
content
.environment(\.uiScale, scale)
.environment(\.bonsplitUIScale, scale)
}
}

extension View {
func withUIScaleEnvironment() -> some View {
modifier(UIScaleEnvironmentModifier())
}
}

// MARK: - AppKit Helper

extension UIScaleSettings {
/// For AppKit NSFont sites that cannot use SwiftUI environment.
static func scaled(_ base: CGFloat, defaults: UserDefaults = .standard) -> CGFloat {
base * resolvedScale(defaults: defaults)
}
}
2 changes: 1 addition & 1 deletion Sources/Update/UpdatePill.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ struct UpdatePill: View {
@ObservedObject var model: UpdateViewModel
@State private var showPopover = false

private let textFont = NSFont.systemFont(ofSize: 11, weight: .medium)
private var textFont: NSFont { NSFont.systemFont(ofSize: UIScaleSettings.scaled(11), weight: .medium) }

var body: some View {
if model.showsPill {
Expand Down
Loading