Skip to content

Commit 98d8de8

Browse files
authored
Font Improvement (#1608)
* Fix missing terminal font issue #1428 * Improvements on text editing font settings * Fix main thread issue and minor improvements * Move business logic to extension * Use Menu for lazy loading * Use task group to get fonts
1 parent 5961018 commit 98d8de8

File tree

7 files changed

+124
-97
lines changed

7 files changed

+124
-97
lines changed

CodeEdit/Features/CodeFile/CodeFileView.swift

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ struct CodeFileView: View {
5252

5353
private let isEditable: Bool
5454

55-
private let systemFont: NSFont = .monospacedSystemFont(ofSize: 11, weight: .medium)
56-
5755
private let undoManager = CEUndoManager()
5856

5957
init(codeFile: CodeFileDocument, textViewCoordinators: [TextViewCoordinator] = [], isEditable: Bool = true) {
@@ -85,9 +83,7 @@ struct CodeFileView: View {
8583

8684
@State private var selectedTheme = ThemeModel.shared.selectedTheme ?? ThemeModel.shared.themes.first!
8785

88-
@State private var font: NSFont = {
89-
return Settings[\.textEditing].font.current()
90-
}()
86+
@State private var font: NSFont = Settings[\.textEditing].font.current
9187

9288
@State private var bracketPairHighlight: BracketPairHighlight? = {
9389
let theme = ThemeModel.shared.selectedTheme ?? ThemeModel.shared.themes.first!
@@ -150,11 +146,8 @@ struct CodeFileView: View {
150146
guard let theme = newValue else { return }
151147
self.selectedTheme = theme
152148
}
153-
.onChange(of: settingsFont) { newValue in
154-
font = NSFont(
155-
name: settingsFont.name,
156-
size: CGFloat(newValue.size)
157-
) ?? systemFont.withSize(CGFloat(newValue.size))
149+
.onChange(of: settingsFont) { newFontSetting in
150+
font = newFontSetting.current
158151
}
159152
.onChange(of: bracketHighlight) { _ in
160153
bracketPairHighlight = getBracketPairHighlight()

CodeEdit/Features/Settings/Pages/TerminalSettings/Models/TerminalSettings.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Nanashi Li on 2022/04/08.
66
//
77

8+
import AppKit
89
import Foundation
910

1011
extension SettingsData {
@@ -89,9 +90,6 @@ extension SettingsData {
8990
}
9091

9192
struct TerminalFont: Codable, Hashable {
92-
/// Indicates whether or not to use a custom font
93-
var customFont: Bool = false
94-
9593
/// The font size for the custom font
9694
var size: Double = 12
9795

@@ -104,9 +102,16 @@ extension SettingsData {
104102
/// Explicit decoder init for setting default values when key is not present in `JSON`
105103
init(from decoder: Decoder) throws {
106104
let container = try decoder.container(keyedBy: CodingKeys.self)
107-
self.customFont = try container.decodeIfPresent(Bool.self, forKey: .customFont) ?? false
108-
self.size = try container.decodeIfPresent(Double.self, forKey: .size) ?? 11
109-
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "SF Mono"
105+
self.size = try container.decodeIfPresent(Double.self, forKey: .size) ?? size
106+
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? name
107+
}
108+
109+
/// Returns an NSFont representation of the current configuration.
110+
///
111+
/// Returns the custom font, if enabled and able to be instantiated.
112+
/// Otherwise returns a default system font monospaced.
113+
var current: NSFont {
114+
return NSFont(name: name, size: size) ?? NSFont.monospacedSystemFont(ofSize: size, weight: .medium)
110115
}
111116
}
112117
}

CodeEdit/Features/Settings/Pages/TerminalSettings/TerminalSettingsView.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@ private extension TerminalSettingsView {
7070

7171
@ViewBuilder private var fontSelector: some View {
7272
MonospacedFontPicker(title: "Font", selectedFontName: $settings.font.name)
73-
.onChange(of: settings.font.name) { fontName in
74-
settings.font.customFont = fontName != "SF Mono"
75-
}
7673
}
7774

7875
private var fontSizeSelector: some View {

CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,6 @@ extension SettingsData {
161161
}
162162

163163
struct EditorFont: Codable, Hashable {
164-
/// Indicates whether or not to use a custom font
165-
var customFont: Bool = false
166-
167164
/// The font size for the font
168165
var size: Double = 12
169166

@@ -176,21 +173,16 @@ extension SettingsData {
176173
/// Explicit decoder init for setting default values when key is not present in `JSON`
177174
init(from decoder: Decoder) throws {
178175
let container = try decoder.container(keyedBy: CodingKeys.self)
179-
self.customFont = try container.decodeIfPresent(Bool.self, forKey: .customFont) ?? false
180-
self.size = try container.decodeIfPresent(Double.self, forKey: .size) ?? 11
181-
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "SF Mono"
176+
self.size = try container.decodeIfPresent(Double.self, forKey: .size) ?? size
177+
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? name
182178
}
183179

184180
/// Returns an NSFont representation of the current configuration.
185181
///
186182
/// Returns the custom font, if enabled and able to be instantiated.
187183
/// Otherwise returns a default system font monospaced.
188-
func current() -> NSFont {
189-
guard customFont,
190-
let customFont = NSFont(name: name, size: size) else {
191-
return NSFont.monospacedSystemFont(ofSize: size, weight: .medium)
192-
}
193-
return customFont
184+
var current: NSFont {
185+
return NSFont(name: name, size: size) ?? NSFont.monospacedSystemFont(ofSize: size, weight: .medium)
194186
}
195187
}
196188
}

CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ struct TextEditingSettingsView: View {
3939
private extension TextEditingSettingsView {
4040
@ViewBuilder private var fontSelector: some View {
4141
MonospacedFontPicker(title: "Font", selectedFontName: $textEditing.font.name)
42-
.onChange(of: textEditing.font.name) { fontName in
43-
textEditing.font.customFont = fontName != "SF Mono"
44-
}
4542
}
4643

4744
@ViewBuilder private var fontSizeSelector: some View {

CodeEdit/Features/Settings/Views/MonospacedFontPicker.swift

Lines changed: 103 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,84 +10,141 @@ import SwiftUI
1010
struct MonospacedFontPicker: View {
1111
var title: String
1212
@Binding var selectedFontName: String
13-
@State var recentFonts: [String]
14-
@State var monospacedFontFamilyNames: [String] = []
15-
@State var otherFontFamilyNames: [String] = []
13+
@State private var recentFonts: [String]
14+
@State private var monospacedFontFamilyNames: [String] = []
15+
@State private var otherFontFamilyNames: [String] = []
1616

1717
init(title: String, selectedFontName: Binding<String>) {
1818
self.title = title
1919
self._selectedFontName = selectedFontName
2020
self.recentFonts = UserDefaults.standard.stringArray(forKey: "recentFonts") ?? []
2121
}
2222

23-
private func pushIntoRecentFonts(_ newItem: String) {
24-
recentFonts.removeAll(where: { $0 == newItem })
25-
recentFonts.insert(newItem, at: 0)
26-
if recentFonts.count > 3 {
27-
recentFonts.removeLast()
28-
}
29-
UserDefaults.standard.set(recentFonts, forKey: "recentFonts")
30-
}
31-
32-
func getFonts() {
33-
DispatchQueue.global(qos: .userInitiated).async {
34-
let availableFontFamilies = NSFontManager.shared.availableFontFamilies
35-
36-
self.monospacedFontFamilyNames = availableFontFamilies.filter { fontFamilyName in
37-
let fontNames = NSFontManager.shared.availableMembers(ofFontFamily: fontFamilyName) ?? []
38-
return fontNames.contains { fontName in
39-
guard let font = NSFont(name: "\(fontName[0])", size: 14) else {
40-
return false
41-
}
42-
return font.isFixedPitch && font.numberOfGlyphs > 26
43-
}
44-
}
45-
.filter { $0 != "SF Mono" }
46-
47-
self.otherFontFamilyNames = availableFontFamilies.filter { fontFamilyName in
48-
let fontNames = NSFontManager.shared.availableMembers(ofFontFamily: fontFamilyName) ?? []
49-
return fontNames.contains { fontName in
50-
guard let font = NSFont(name: "\(fontName[0])", size: 14) else {
51-
return false
52-
}
53-
return !font.isFixedPitch && font.numberOfGlyphs > 26
54-
}
55-
}
56-
}
57-
}
58-
5923
var body: some View {
60-
return Picker(selection: $selectedFontName, label: Text(title)) {
24+
Picker(selection: $selectedFontName, label: Text(title)) {
6125
Text("System Font")
6226
.font(Font(NSFont.monospacedSystemFont(ofSize: 13.5, weight: .medium)))
6327
.tag("SF Mono")
28+
6429
if !recentFonts.isEmpty {
6530
Divider()
6631
ForEach(recentFonts, id: \.self) { fontFamilyName in
67-
Text(fontFamilyName).font(.custom(fontFamilyName, size: 13.5))
32+
Text(fontFamilyName)
33+
.font(.custom(fontFamilyName, size: 13.5))
34+
.tag(fontFamilyName) // to prevent picker invalid and does not have an associated tag error.
6835
}
6936
}
37+
7038
if !monospacedFontFamilyNames.isEmpty {
7139
Divider()
7240
ForEach(monospacedFontFamilyNames, id: \.self) { fontFamilyName in
73-
Text(fontFamilyName).font(.custom(fontFamilyName, size: 13.5))
41+
Text(fontFamilyName)
42+
.font(.custom(fontFamilyName, size: 13.5))
43+
.tag(fontFamilyName)
7444
}
7545
}
46+
7647
if !otherFontFamilyNames.isEmpty {
7748
Divider()
78-
Picker(selection: $selectedFontName, label: Text("Other fonts...")) {
49+
Menu {
7950
ForEach(otherFontFamilyNames, id: \.self) { fontFamilyName in
80-
Text(fontFamilyName)
81-
.font(.custom(fontFamilyName, size: 13.5))
51+
Button {
52+
pushIntoRecentFonts(fontFamilyName)
53+
selectedFontName = fontFamilyName
54+
} label: {
55+
Text(fontFamilyName)
56+
.font(.custom(fontFamilyName, size: 13.5))
57+
}
58+
.tag(fontFamilyName)
8259
}
60+
} label: {
61+
Text("Other fonts...")
8362
}
8463
}
8564
}
8665
.onChange(of: selectedFontName) { _ in
8766
if selectedFontName != "SF Mono" {
8867
pushIntoRecentFonts(selectedFontName)
68+
69+
// remove the font to prevent ForEach conflict
70+
monospacedFontFamilyNames.removeAll { $0 == selectedFontName }
71+
otherFontFamilyNames.removeAll { $0 == selectedFontName }
72+
}
73+
}
74+
.task {
75+
await getFonts()
76+
}
77+
}
78+
}
79+
80+
extension MonospacedFontPicker {
81+
private func pushIntoRecentFonts(_ newItem: String) {
82+
recentFonts.removeAll(where: { $0 == newItem })
83+
recentFonts.insert(newItem, at: 0)
84+
if recentFonts.count > 3 {
85+
recentFonts.removeLast()
86+
}
87+
UserDefaults.standard.set(recentFonts, forKey: "recentFonts")
88+
}
89+
90+
private func getFonts() async {
91+
await withTaskGroup(of: Void.self) { group in
92+
group.addTask {
93+
let monospacedFontFamilyNames = getMonospacedFamilyNames()
94+
await MainActor.run {
95+
self.monospacedFontFamilyNames = monospacedFontFamilyNames
96+
}
97+
}
98+
99+
group.addTask {
100+
let otherFontFamilyNames = getOtherFontFamilyNames()
101+
await MainActor.run {
102+
self.otherFontFamilyNames = otherFontFamilyNames
103+
}
89104
}
90105
}
91-
.onAppear(perform: getFonts)
92106
}
107+
108+
private func getMonospacedFamilyNames() -> [String] {
109+
let availableFontFamilies = NSFontManager.shared.availableFontFamilies
110+
111+
return availableFontFamilies.filter { fontFamilyName in
112+
// exclude the font if it is in recentFonts to prevent ForEach conflict
113+
if recentFonts.contains(fontFamilyName) {
114+
return false
115+
}
116+
117+
// exclude default font
118+
if fontFamilyName == "SF Mono" {
119+
return false
120+
}
121+
122+
// include the font which is fixedPitch
123+
// include the font which numberOfGlyphs is greater than 26
124+
if let font = NSFont(name: fontFamilyName, size: 14) {
125+
return font.isFixedPitch && font.numberOfGlyphs > 26
126+
} else {
127+
return false
128+
}
129+
}
130+
}
131+
132+
private func getOtherFontFamilyNames() -> [String] {
133+
let availableFontFamilies = NSFontManager.shared.availableFontFamilies
134+
135+
return availableFontFamilies.filter { fontFamilyName in
136+
// exclude the font if it is in recentFonts to prevent ForEach conflict
137+
if recentFonts.contains(fontFamilyName) {
138+
return false
139+
}
140+
141+
// include the font which is NOT fixedPitch
142+
// include the font which numberOfGlyphs is greater than 26
143+
if let font = NSFont(name: fontFamilyName, size: 14) {
144+
return !font.isFixedPitch && font.numberOfGlyphs > 26
145+
} else {
146+
return false
147+
}
148+
}
149+
}
93150
}

CodeEdit/Features/TerminalEmulator/TerminalEmulatorView.swift

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,12 @@ struct TerminalEmulatorView: NSViewRepresentable {
2727

2828
@State var terminal: LocalProcessTerminalView
2929

30-
private let systemFont: NSFont = .monospacedSystemFont(ofSize: 11, weight: .medium)
31-
3230
private var font: NSFont {
3331
if terminalSettings.useTextEditorFont {
34-
if !fontSettings.customFont {
35-
return systemFont.withSize(CGFloat(fontSettings.size))
36-
}
37-
return NSFont(
38-
name: fontSettings.name,
39-
size: CGFloat(fontSettings.size)
40-
) ?? systemFont
41-
}
42-
43-
if !terminalSettings.font.customFont {
44-
return systemFont.withSize(CGFloat(terminalSettings.font.size))
32+
return fontSettings.current
33+
} else {
34+
return terminalSettings.font.current
4535
}
46-
return NSFont(
47-
name: terminalSettings.font.name,
48-
size: CGFloat(terminalSettings.font.size)
49-
) ?? systemFont
5036
}
5137

5238
private var url: URL

0 commit comments

Comments
 (0)