Skip to content
Open
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
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
107 changes: 53 additions & 54 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