diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 393bec64b0..bcb9982454 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -331,6 +331,9 @@ 6CFF967829BEBCF600182D6F /* MainCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967729BEBCF600182D6F /* MainCommands.swift */; }; 6CFF967A29BEBD2400182D6F /* ViewCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967929BEBD2400182D6F /* ViewCommands.swift */; }; 6CFF967C29BEBD5200182D6F /* WindowCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967B29BEBD5200182D6F /* WindowCommands.swift */; }; + 850C631029D6B01D00E1444C /* VenturaPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850C630F29D6B01D00E1444C /* VenturaPreferences.swift */; }; + 850C631229D6B03400E1444C /* Page.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850C631129D6B03400E1444C /* Page.swift */; }; + 850C631829D6D56800E1444C /* VenturaColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850C631729D6D56800E1444C /* VenturaColors.swift */; }; B60BE8BD297A167600841125 /* AcknowledgementRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */; }; B658FB3427DA9E1000EA4DBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3327DA9E1000EA4DBD /* Assets.xcassets */; }; B658FB3727DA9E1000EA4DBD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */; }; @@ -729,6 +732,9 @@ 6CFF967729BEBCF600182D6F /* MainCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCommands.swift; sourceTree = ""; }; 6CFF967929BEBD2400182D6F /* ViewCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCommands.swift; sourceTree = ""; }; 6CFF967B29BEBD5200182D6F /* WindowCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowCommands.swift; sourceTree = ""; }; + 850C630F29D6B01D00E1444C /* VenturaPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VenturaPreferences.swift; sourceTree = ""; }; + 850C631129D6B03400E1444C /* Page.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Page.swift; sourceTree = ""; }; + 850C631729D6D56800E1444C /* VenturaColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VenturaColors.swift; sourceTree = ""; }; B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementRowView.swift; sourceTree = ""; }; B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeEdit.app; sourceTree = BUILT_PRODUCTS_DIR; }; B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WorkspaceView.swift; sourceTree = ""; tabWidth = 4; }; @@ -2272,6 +2278,9 @@ 58D01C85293167DC00C5B6B4 /* Utils */, 6C5AB9D629C1496E003B5F96 /* SceneID.swift */, 0468438427DC76E200F8E88E /* AppDelegate.swift */, + 850C631729D6D56800E1444C /* VenturaColors.swift */, + 850C630F29D6B01D00E1444C /* VenturaPreferences.swift */, + 850C631129D6B03400E1444C /* Page.swift */, 5C4BB1E028212B1E00A92FB2 /* World.swift */, B658FB3327DA9E1000EA4DBD /* Assets.xcassets */, B658FB3827DA9E1000EA4DBD /* CodeEdit.entitlements */, @@ -2716,6 +2725,7 @@ 587B9E7B29301D8F00AC7927 /* GitHubRouter.swift in Sources */, 201169E22837B3D800F92B46 /* SourceControlNavigatorChangesView.swift in Sources */, 20EBB507280C32D300F3A5DA /* QuickHelpInspectorView.swift in Sources */, + 850C631029D6B01D00E1444C /* VenturaPreferences.swift in Sources */, DE6405A62817734700881FDF /* TabBarNative.swift in Sources */, 587B9E5C29301D8F00AC7927 /* Parameters.swift in Sources */, 58798235292E30B90085B254 /* FeedbackModel.swift in Sources */, @@ -2735,6 +2745,8 @@ 587D9B782933BF5700BF7490 /* Interface.swift in Sources */, 6CFF967A29BEBD2400182D6F /* ViewCommands.swift in Sources */, 2072FA1E280D891500C7F8D4 /* FileLocation.swift in Sources */, + 850C631229D6B03400E1444C /* Page.swift in Sources */, + 850C631829D6D56800E1444C /* VenturaColors.swift in Sources */, 587B9E6729301D8F00AC7927 /* GitLabEventData.swift in Sources */, B66A4E4529C8E86D004573B4 /* CommandsFixes.swift in Sources */, 5882252F292C280D00E83CDE /* StatusBarClearButton.swift in Sources */, diff --git a/CodeEdit/AppDelegate.swift b/CodeEdit/AppDelegate.swift index 8f8603232b..85ce1dd960 100644 --- a/CodeEdit/AppDelegate.swift +++ b/CodeEdit/AppDelegate.swift @@ -211,98 +211,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } // MARK: - Preferences - private lazy var preferencesWindowController = PreferencesWindowController( + private lazy var preferencesWindowController: PreferencesWindowController = PreferencesWindowController( panes: [ + // TODO: Remove this and just use VenturaPreferences() Preferences.Pane( - identifier: Preferences.PaneIdentifier("GeneralSettings"), - title: "General", + identifier: Preferences.PaneIdentifier("Preferences"), + title: "Preferences", toolbarIcon: NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil)! ) { - GeneralPreferencesView() - .environmentObject(updater) - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("Accounts"), - title: "Accounts", - toolbarIcon: NSImage(systemSymbolName: "at", accessibilityDescription: nil)! - ) { - AccountPreferencesView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("Behaviors"), - title: "Behaviors", - toolbarIcon: NSImage(systemSymbolName: "flowchart", accessibilityDescription: nil)! - ) { - PreferencesPlaceholderView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("Navigation"), - title: "Navigation", - toolbarIcon: NSImage( - systemSymbolName: "arrow.triangle.turn.up.right.diamond", - accessibilityDescription: nil - )! - ) { - PreferencesPlaceholderView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("Themes"), - title: "Themes", - toolbarIcon: NSImage(systemSymbolName: "paintbrush", accessibilityDescription: nil)! - ) { - ThemePreferencesView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("TextEditing"), - title: "Text Editing", - toolbarIcon: NSImage(systemSymbolName: "square.and.pencil", accessibilityDescription: nil)! - ) { - TextEditingPreferencesView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("Terminal"), - title: "Terminal", - toolbarIcon: NSImage(systemSymbolName: "terminal", accessibilityDescription: nil)! - ) { - TerminalPreferencesView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("KeyBindings"), - title: "Key Bindings", - toolbarIcon: NSImage(systemSymbolName: "keyboard", accessibilityDescription: nil)! - ) { - KeybindingsPreferencesView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("SourceControl"), - title: "Source Control", - toolbarIcon: NSImage.vault - ) { - SourceControlPreferencesView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("Components"), - title: "Components", - toolbarIcon: NSImage(systemSymbolName: "puzzlepiece", accessibilityDescription: nil)! - ) { - PreferencesPlaceholderView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("Locations"), - title: "Locations", - toolbarIcon: NSImage(systemSymbolName: "externaldrive", accessibilityDescription: nil)! - ) { - LocationsPreferencesView() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier("Advanced"), - title: "Advanced", - toolbarIcon: NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: nil)! - ) { - PreferencesPlaceholderView() + VenturaPreferences(updater: updater) } ], - animated: false + animated: true ) // MARK: NSDocumentController delegate diff --git a/CodeEdit/Features/AppPreferences/Sections/AccountsPreferences/AccountPreferencesView.swift b/CodeEdit/Features/AppPreferences/Sections/AccountsPreferences/AccountPreferencesView.swift index f70e0996b0..90e08f5f88 100644 --- a/CodeEdit/Features/AppPreferences/Sections/AccountsPreferences/AccountPreferencesView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/AccountsPreferences/AccountPreferencesView.swift @@ -7,8 +7,14 @@ import SwiftUI -// swiftlint:disable for_where struct AccountPreferencesView: View { + + // MARK: - View + + var body: some View { + accountsSection + } + @StateObject private var prefs: AppPreferencesModel = .shared @@ -20,8 +26,14 @@ struct AccountPreferencesView: View { @State var accountSelection: SourceControlAccounts.ID? +} - var body: some View { +// swiftlint:disable for_where +private extension AccountPreferencesView { + + // MARK: - Sections + + private var accountsSection: some View { VStack(alignment: .leading, spacing: 16) { HStack(spacing: 1) { accountSelectionView @@ -41,6 +53,8 @@ struct AccountPreferencesView: View { } } + // MARK: - Preference Views + private var accountSelectionView: some View { VStack(alignment: .leading, spacing: 1) { PreferencesToolbar { @@ -159,6 +173,8 @@ struct AccountPreferencesView: View { .background(EffectView(.contentBackground)) } + // MARK: - Functions + private func getSourceControlAccount(selectedAccountId: String) -> SourceControlAccounts? { let gitAccounts = prefs.preferences.accounts.sourceControlAccounts.gitAccount return gitAccounts.first { $0.id == selectedAccountId } @@ -174,5 +190,4 @@ struct AccountPreferencesView: View { } } } - } diff --git a/CodeEdit/Features/AppPreferences/Sections/GeneralPreferences/GeneralPreferencesView.swift b/CodeEdit/Features/AppPreferences/Sections/GeneralPreferences/GeneralPreferencesView.swift index 9221beae2b..bee739f963 100644 --- a/CodeEdit/Features/AppPreferences/Sections/GeneralPreferences/GeneralPreferencesView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/GeneralPreferences/GeneralPreferencesView.swift @@ -9,29 +9,7 @@ import SwiftUI /// A view that implements the `General` preference section struct GeneralPreferencesView: View { - private let inputWidth: Double = 160 - private let textEditorWidth: Double = 220 - private let textEditorHeight: Double = 30 - - @EnvironmentObject - var updater: SoftwareUpdater - - @StateObject - private var prefs: AppPreferencesModel = .shared - - @State - private var openInCodeEdit: Bool = true - - init() { - guard let defaults = UserDefaults.init( - suiteName: "austincondiff.CodeEdit.shared" - ) else { - print("Failed to get/init shared defaults") - return - } - - self.openInCodeEdit = defaults.bool(forKey: "enableOpenInCE") - } + // MARK: - View var body: some View { PreferencesContent { @@ -58,11 +36,38 @@ struct GeneralPreferencesView: View { updaterSection } } - .frame(minHeight: 650) + .frame(width: 715) + } + + private let inputWidth: Double = 160 + private let textEditorWidth: Double = 220 + private let textEditorHeight: Double = 30 + + @EnvironmentObject + var updater: SoftwareUpdater + + @StateObject + private var prefs: AppPreferencesModel = .shared + + @State + private var openInCodeEdit: Bool = true + + init() { + guard let defaults = UserDefaults.init( + suiteName: "austincondiff.CodeEdit.shared" + ) else { + print("Failed to get/init shared defaults") + return + } + + self.openInCodeEdit = defaults.bool(forKey: "enableOpenInCE") } } +/// The extension of the view with all the preferences private extension GeneralPreferencesView { + // MARK: - Sections + var appearanceSection: some View { PreferencesSection("Appearance") { Picker("Appearance", selection: $prefs.preferences.general.appAppearance) { @@ -294,6 +299,18 @@ private extension GeneralPreferencesView { } } + var autoSaveSection: some View { + PreferencesSection("Auto Save Behavior", hideLabels: false) { + Toggle( + "Automatically save changes to disk", + isOn: $prefs.preferences.general.isAutoSaveOn + ) + .toggleStyle(.checkbox) + } + } + + // MARK: - Preference Views + private var lastUpdatedString: String { if let lastUpdatedDate = updater.lastUpdateCheckDate { return Self.formatter.string(from: lastUpdatedDate) @@ -308,21 +325,6 @@ private extension GeneralPreferencesView { return copy } - private static let formatter = configure(DateFormatter()) { - $0.dateStyle = .medium - $0.timeStyle = .medium - } - - var autoSaveSection: some View { - PreferencesSection("Auto Save Behavior", hideLabels: false) { - Toggle( - "Automatically save changes to disk", - isOn: $prefs.preferences.general.isAutoSaveOn - ) - .toggleStyle(.checkbox) - } - } - func fallbackShellInstallation(commandPath: String, destinationPath: String) { let cmd = [ "osascript", @@ -372,4 +374,11 @@ private extension GeneralPreferencesView { .toggleStyle(.checkbox) } } + + // MARK: - Formatters + + private static let formatter = configure(DateFormatter()) { + $0.dateStyle = .medium + $0.timeStyle = .medium + } } diff --git a/CodeEdit/Features/AppPreferences/Sections/KeybindingsPreferences/KeybindingsPreferencesView.swift b/CodeEdit/Features/AppPreferences/Sections/KeybindingsPreferences/KeybindingsPreferencesView.swift index 88cb9ef917..a8a38409f2 100644 --- a/CodeEdit/Features/AppPreferences/Sections/KeybindingsPreferences/KeybindingsPreferencesView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/KeybindingsPreferences/KeybindingsPreferencesView.swift @@ -8,9 +8,27 @@ import SwiftUI struct KeybindingsPreferencesView: View { + + // MARK: - View + var body: some View { + mainSection + } +} + +private extension KeybindingsPreferencesView { + + // MARK: - Sections + + private var mainSection: some View { PreferencesContent { - Text("Implementation needed") + implementationNeededText } } + + // MARK: - Preference Views + + private var implementationNeededText: some View { + Text("Implementation needed") + } } diff --git a/CodeEdit/Features/AppPreferences/Sections/LocationsPreferences/LocationsPreferencesView.swift b/CodeEdit/Features/AppPreferences/Sections/LocationsPreferences/LocationsPreferencesView.swift index a9cd5e0499..14bcd216bf 100644 --- a/CodeEdit/Features/AppPreferences/Sections/LocationsPreferences/LocationsPreferencesView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/LocationsPreferences/LocationsPreferencesView.swift @@ -10,37 +10,64 @@ import Preferences /// A view that implements the `Locations` preference section struct LocationsPreferencesView: View { + + // MARK: - View + var body: some View { PreferencesContent { - PreferencesSection("Preferences Location") { - HStack { - Text(AppPreferencesModel.shared.baseURL.path) - .foregroundColor(.secondary) - Button { - NSWorkspace.shared.selectFile( - nil, - inFileViewerRootedAtPath: AppPreferencesModel.shared.baseURL.path - ) - } label: { - Image(systemName: "arrow.right.circle.fill") - } - .buttonStyle(.plain) - .foregroundColor(.secondary) - } + preferencesLocationSection + themesLocationSection + } + } +} + +private extension LocationsPreferencesView { + + // MARK: - Sections + + private var preferencesLocationSection: some View { + PreferencesSection("Preferences Location") { + preferencesLocation + } + } + + private var themesLocationSection: some View { + PreferencesSection("Themes Location") { + themesLocation + } + } + + // MARK: - Preference Views + + @ViewBuilder + private var preferencesLocation: some View { + HStack { + Text(AppPreferencesModel.shared.baseURL.path) + .foregroundColor(.secondary) + Button { + NSWorkspace.shared.selectFile( + nil, + inFileViewerRootedAtPath: AppPreferencesModel.shared.baseURL.path + ) + } label: { + Image(systemName: "arrow.right.circle.fill") } - PreferencesSection("Themes Location") { - HStack { - Text(ThemeModel.shared.themesURL.path) - .foregroundColor(.secondary) - Button { - NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: ThemeModel.shared.themesURL.path) - } label: { - Image(systemName: "arrow.right.circle.fill") - } - .buttonStyle(.plain) - .foregroundColor(.secondary) - } + .buttonStyle(.plain) + .foregroundColor(.secondary) + } + } + + private var themesLocation: some View { + HStack { + Text(ThemeModel.shared.themesURL.path) + .foregroundColor(.secondary) + Button { + NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: ThemeModel.shared.themesURL.path) + } label: { + Image(systemName: "arrow.right.circle.fill") } + .buttonStyle(.plain) + .foregroundColor(.secondary) } } } diff --git a/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlGeneralView.swift b/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlGeneralView.swift index 81c6966ce8..0abf5aed96 100644 --- a/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlGeneralView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlGeneralView.swift @@ -8,6 +8,22 @@ import SwiftUI struct SourceControlGeneralView: View { + + // MARK: - View + + var body: some View { + VStack { + sourceControlSection + textEditingSection + reportingSection + comparisonViewSection + sourceControlNavigator + defaultBranchNameSection + } + .frame(width: 715) + .background(EffectView(.contentBackground)) + } + private let inputWidth: Double = 200 @StateObject @@ -18,97 +34,156 @@ struct SourceControlGeneralView: View { @State var branchName: String +} - var body: some View { - VStack { +private extension SourceControlGeneralView { - PreferencesSection("Source Control", hideLabels: false) { - Toggle("Enable Source Control", isOn: $prefs.preferences.sourceControl.general.enableSourceControl) - .toggleStyle(.checkbox) - - VStack(alignment: .leading) { - Toggle( - "Refresh local status automatically", - isOn: $prefs.preferences.sourceControl.general.refreshStatusLocally - ) - .toggleStyle(.checkbox) - Toggle( - "Fetch and refresh server status automatically", - isOn: $prefs.preferences.sourceControl.general.fetchRefreshServerStatus - ) - .toggleStyle(.checkbox) - Toggle( - "Add and remove files automatically", - isOn: $prefs.preferences.sourceControl.general.addRemoveAutomatically - ) - .toggleStyle(.checkbox) - Toggle( - "Select files to commit automatically", - isOn: $prefs.preferences.sourceControl.general.selectFilesToCommit - ) - .toggleStyle(.checkbox) - } - .padding(.leading, 20) - } + // MARK: - Sections - PreferencesSection("Text Editing", hideLabels: false) { - Toggle( - "Show Source Control changes", - isOn: $prefs.preferences.sourceControl.general.showSourceControlChanges - ) - .toggleStyle(.checkbox) - - Toggle( - "Include upstream changes", - isOn: $prefs.preferences.sourceControl.general.includeUpstreamChanges - ) - .toggleStyle(.checkbox) - .padding(.leading, 20) - } + private var sourceControlSection: some View { + PreferencesSection("Source Control", hideLabels: false) { + enableSourceControl - PreferencesSection("Reporting", hideLabels: false) { - Toggle( - "Open created issue in the browser", - isOn: $prefs.preferences.sourceControl.general.openFeedbackInBrowser - ) - .toggleStyle(.checkbox) + VStack(alignment: .leading) { + refreshLocalStatusAuto + fetchRefreshStatusAuto + addRemoveFilesAuto + selectFilesToCommitAuto } + .padding(.leading, 20) + } + } - PreferencesSection("Comparison View", hideLabels: true) { - Picker( - "Comparison View", - selection: $prefs.preferences.sourceControl.general.revisionComparisonLayout - ) { - Text("Local Revision on Left Side") - .tag(AppPreferences.RevisionComparisonLayout.localLeft) - Text("Local Revision on Right Side") - .tag(AppPreferences.RevisionComparisonLayout.localRight) - } - .frame(width: inputWidth) - } + private var textEditingSection: some View { + PreferencesSection("Text Editing", hideLabels: false) { + showSourceControlChanges + includeUpstreamChanges + } + } - PreferencesSection("Source Control Navigator", hideLabels: true) { - Picker( - "Source Control Navigator", - selection: $prefs.preferences.sourceControl.general.controlNavigatorOrder - ) { - Text("Sort by Name") - .tag(AppPreferences.ControlNavigatorOrder.sortByName) - Text("Sort by Date") - .tag(AppPreferences.ControlNavigatorOrder.sortByDate) - } - .frame(width: inputWidth) - } + private var reportingSection: some View { + PreferencesSection("Reporting", hideLabels: false) { + openCreatedIssueInBrowser + } + } - PreferencesSection("Default Branch Name", hideLabels: false) { - TextField("main", text: $branchName) - .frame(width: inputWidth) - Text("Branch names cannot contain spaces, backslashes, or other symbols") - .font(.system(size: 12)) - .foregroundColor(.secondary) - } + private var comparisonViewSection: some View { + PreferencesSection("Comparison View", hideLabels: true) { + comparisonView + } + } + + private var sourceControlNavigatorSection: some View { + PreferencesSection("Source Control Navigator", hideLabels: true) { + sourceControlNavigator + } + } + + private var defaultBranchNameSection: some View { + PreferencesSection("Default Branch Name", hideLabels: false) { + defaultBranchName + } + } + + // MARK: - Preference Views + + private var enableSourceControl: some View { + Toggle( + "Enable Source Control", + isOn: $prefs.preferences.sourceControl.general.enableSourceControl + ) + .toggleStyle(.checkbox) + } + + private var refreshLocalStatusAuto: some View { + Toggle( + "Refresh local status automatically", + isOn: $prefs.preferences.sourceControl.general.refreshStatusLocally + ) + .toggleStyle(.checkbox) + } + + private var fetchRefreshStatusAuto: some View { + Toggle( + "Fetch and refresh server status automatically", + isOn: $prefs.preferences.sourceControl.general.fetchRefreshServerStatus + ) + .toggleStyle(.checkbox) + } + + private var addRemoveFilesAuto: some View { + Toggle( + "Add and remove files automatically", + isOn: $prefs.preferences.sourceControl.general.addRemoveAutomatically + ) + .toggleStyle(.checkbox) + } + + private var selectFilesToCommitAuto: some View { + Toggle( + "Select files to commit automatically", + isOn: $prefs.preferences.sourceControl.general.selectFilesToCommit + ) + .toggleStyle(.checkbox) + } + + private var showSourceControlChanges: some View { + Toggle( + "Show Source Control changes", + isOn: $prefs.preferences.sourceControl.general.showSourceControlChanges + ) + .toggleStyle(.checkbox) + } + + private var includeUpstreamChanges: some View { + Toggle( + "Include upstream changes", + isOn: $prefs.preferences.sourceControl.general.includeUpstreamChanges + ) + .toggleStyle(.checkbox) + .padding(.leading, 20) + } + + private var openCreatedIssueInBrowser: some View { + Toggle( + "Open created issue in the browser", + isOn: $prefs.preferences.sourceControl.general.openFeedbackInBrowser + ) + .toggleStyle(.checkbox) + } + + private var comparisonView: some View { + Picker( + "Comparison View", + selection: $prefs.preferences.sourceControl.general.revisionComparisonLayout + ) { + Text("Local Revision on Left Side") + .tag(AppPreferences.RevisionComparisonLayout.localLeft) + Text("Local Revision on Right Side") + .tag(AppPreferences.RevisionComparisonLayout.localRight) + } + .frame(width: inputWidth) + } + + private var sourceControlNavigator: some View { + Picker( + "Source Control Navigator", + selection: $prefs.preferences.sourceControl.general.controlNavigatorOrder + ) { + Text("Sort by Name") + .tag(AppPreferences.ControlNavigatorOrder.sortByName) + Text("Sort by Date") + .tag(AppPreferences.ControlNavigatorOrder.sortByDate) } - .frame(height: 350) - .background(EffectView(.contentBackground)) + .frame(width: inputWidth) + } + + @ViewBuilder + private var defaultBranchName: some View { + TextField("main", text: $branchName) + .frame(width: inputWidth) + Text("Branch names cannot contain spaces, backslashes, or other symbols") + .font(.system(size: 12)) + .foregroundColor(.secondary) } } diff --git a/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlGitView.swift b/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlGitView.swift index 11cc88c0da..b2d09bc84c 100644 --- a/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlGitView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlGitView.swift @@ -8,6 +8,20 @@ import SwiftUI struct SourceControlGitView: View { + + // MARK: - View + + var body: some View { + VStack { + authorNameSection + authorEmailSection + ignoredFilesSection + optionsSection + } + .frame(width: 715) + .background(EffectView(.contentBackground)) + } + private let inputWidth: Double = 280 @StateObject @@ -15,61 +29,91 @@ struct SourceControlGitView: View { @State var ignoredFileSelection: IgnoredFiles.ID? +} - var body: some View { - VStack { - PreferencesSection("Author Name", hideLabels: false) { - TextField("Git Author Name", text: $prefs.preferences.sourceControl.git.authorName) - .frame(width: inputWidth) - } +private extension SourceControlGitView { - PreferencesSection("Author Email", hideLabels: false) { - TextField("Git Email", text: $prefs.preferences.sourceControl.git.authorEmail) - .frame(width: inputWidth) - } + // MARK: - Sections + + private var authorNameSection: some View { + PreferencesSection("Author Name", hideLabels: false) { + gitAuthorName + } + } - PreferencesSection("Ignored Files", hideLabels: false, align: .top) { - VStack(spacing: 1) { - List( - $prefs.preferences.sourceControl.git.ignoredFiles, - selection: $ignoredFileSelection - ) { ignoredFile in - IgnoredFileView(ignoredFile: ignoredFile) - } - .overlay(Group { - if prefs.preferences.sourceControl.git.ignoredFiles.isEmpty { - Text("No Ignored Files") - .foregroundColor(.secondary) - .font(.system(size: 11)) - } - }) - .frame(height: 150) - PreferencesToolbar(height: 22) { - bottomToolbar - } - } - .frame(width: inputWidth) - .padding(1) - .background(Rectangle().foregroundColor(Color(NSColor.separatorColor))) + private var authorEmailSection: some View { + PreferencesSection("Author Email", hideLabels: false) { + gitEmail + } + } + + private var ignoredFilesSection: some View { + PreferencesSection("Ignored Files", hideLabels: false, align: .top) { + VStack(spacing: 1) { + ignoredFiles } + .frame(width: inputWidth) + .padding(1) + .background(Rectangle().foregroundColor(Color(NSColor.separatorColor))) + } + } + + private var optionsSection: some View { + PreferencesSection("Options", hideLabels: false) { + preferToRebaseWhenPulling + showMergeCommitsInPerFileLog + } + } + + // MARK: - Preference Views + + private var gitAuthorName: some View { + TextField("Git Author Name", text: $prefs.preferences.sourceControl.git.authorName) + .frame(width: inputWidth) + } + + private var gitEmail: some View { + TextField("Git Email", text: $prefs.preferences.sourceControl.git.authorEmail) + .frame(width: inputWidth) + } - PreferencesSection("Options", hideLabels: false) { - Toggle( - "Prefer to rebase when pulling", - isOn: $prefs.preferences.sourceControl.git.preferRebaseWhenPulling - ) - .toggleStyle(.checkbox) - .frame(width: inputWidth, alignment: .leading) - Toggle( - "Show merge commits in per-file log", - isOn: $prefs.preferences.sourceControl.git.showMergeCommitsPerFileLog - ) - .toggleStyle(.checkbox) - .frame(width: inputWidth, alignment: .leading) + @ViewBuilder + private var ignoredFiles: some View { + List( + $prefs.preferences.sourceControl.git.ignoredFiles, + selection: $ignoredFileSelection + ) { ignoredFile in + IgnoredFileView(ignoredFile: ignoredFile) + } + .overlay(Group { + if prefs.preferences.sourceControl.git.ignoredFiles.isEmpty { + Text("No Ignored Files") + .foregroundColor(.secondary) + .font(.system(size: 11)) } + }) + .frame(height: 150) + PreferencesToolbar(height: 22) { + bottomToolbar } - .frame(height: 350) - .background(EffectView(.contentBackground)) + } + + private var preferToRebaseWhenPulling: some View { + Toggle( + "Prefer to rebase when pulling", + isOn: $prefs.preferences.sourceControl.git.preferRebaseWhenPulling + ) + .toggleStyle(.checkbox) + .frame(width: inputWidth, alignment: .leading) + } + + private var showMergeCommitsInPerFileLog: some View { + Toggle( + "Show merge commits in per-file log", + isOn: $prefs.preferences.sourceControl.git.showMergeCommitsPerFileLog + ) + .toggleStyle(.checkbox) + .frame(width: inputWidth, alignment: .leading) } private var bottomToolbar: some View { diff --git a/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlPreferencesView.swift b/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlPreferencesView.swift index 44e97ea59e..9149e4ce43 100644 --- a/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlPreferencesView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/SourceControlPreferences/SourceControlPreferencesView.swift @@ -8,10 +8,23 @@ import SwiftUI struct SourceControlPreferencesView: View { + + // MARK: - View + + var body: some View { + sourceControlSelector + .frame(width: 715) + } + @State private var selectedSection: Int = 0 +} - var body: some View { +extension SourceControlPreferencesView { + + // MARK: - Preference Views + + private var sourceControlSelector: some View { VStack(alignment: .leading, spacing: 16) { VStack(spacing: 1) { PreferencesToolbar { diff --git a/CodeEdit/Features/AppPreferences/Sections/TerminalPreferences/TerminalPreferencesView.swift b/CodeEdit/Features/AppPreferences/Sections/TerminalPreferences/TerminalPreferencesView.swift index 2c81484c8f..c45249d190 100644 --- a/CodeEdit/Features/AppPreferences/Sections/TerminalPreferences/TerminalPreferencesView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/TerminalPreferences/TerminalPreferencesView.swift @@ -10,29 +10,53 @@ import Preferences /// A view that implements the `Terminal` preference section struct TerminalPreferencesView: View { + + // MARK: - View + + var body: some View { + PreferencesContent { + shellSection + fontSection + cursorSection + } + .frame(width: 715) + } + private let inputWidth: Double = 150 @StateObject private var prefs: AppPreferencesModel = .shared init() {} +} - var body: some View { - PreferencesContent { - PreferencesSection("Shell") { - shellSelector - optionAsMetaToggle - } - PreferencesSection("Font") { - fontSelector - } - PreferencesSection("Cursor") { - cursorStyle - cursorBlink - } +private extension TerminalPreferencesView { + + // MARK: - Sections + + private var shellSection: some View { + PreferencesSection("Shell") { + shellSelector + optionAsMetaToggle + } + } + + private var fontSection: some View { + PreferencesSection("Font") { + fontSelector + } + } + + private var cursorSection: some View { + PreferencesSection("Cursor") { + cursorStyle + cursorBlink } } + // MARK: - Preference Views + + @ViewBuilder private var shellSelector: some View { Picker("Shell:", selection: $prefs.preferences.terminal.shell) { Text("System Default") @@ -45,6 +69,7 @@ struct TerminalPreferencesView: View { } .frame(width: inputWidth) } + private var cursorStyle: some View { Picker("Terminal Cursor Style: ", selection: $prefs.preferences.terminal.cursorStyle) { Text("Block") diff --git a/CodeEdit/Features/AppPreferences/Sections/TextEditingPreferences/TextEditingPreferencesView.swift b/CodeEdit/Features/AppPreferences/Sections/TextEditingPreferences/TextEditingPreferencesView.swift index 2034f6ee42..aef1651b62 100644 --- a/CodeEdit/Features/AppPreferences/Sections/TextEditingPreferences/TextEditingPreferencesView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/TextEditingPreferences/TextEditingPreferencesView.swift @@ -9,89 +9,78 @@ import SwiftUI /// A view that implements the `Text Editing` preference section struct TextEditingPreferencesView: View { + + // MARK: - View + + var body: some View { + PreferencesContent { + tabWidthSection + fontSection + lineHeightSection + codeCompletionSection + lineWrappingSection + } + .frame(width: 715) + } + @StateObject private var prefs: AppPreferencesModel = .shared +} - /// only allows integer values in the range of `[1...8]` - private var tabWidthFormatter: NumberFormatter { - let formatter = NumberFormatter() - formatter.allowsFloats = false - formatter.minimum = 1 - formatter.maximum = 8 +private extension TextEditingPreferencesView { - return formatter - } + // MARK: - Sections - /// only allows float values in the range of `[0.75...2.00]` - /// and formats to 2 decimal places. - private var lineHeightFormatter: NumberFormatter { - let formatter = NumberFormatter() - formatter.allowsFloats = true - formatter.maximumFractionDigits = 2 - formatter.minimumFractionDigits = 2 - formatter.minimum = 0.75 - formatter.maximum = 2.0 + private var tabWidthSection: some View { + PreferencesSection("Default Tab Width") { + defaultTabWidth + } + } - return formatter + private var fontSection: some View { + PreferencesSection("Font") { + fontSelector + fontSizeSelector + } } - private var fontSizeFormatter: NumberFormatter { - let formatter = NumberFormatter() - formatter.allowsFloats = false - formatter.minimum = 1 - formatter.maximum = 288 + private var lineHeightSection: some View { + PreferencesSection("Line Height") { + lineHeight + } + } - return formatter + private var codeCompletionSection: some View { + PreferencesSection("Code completion") { + autocompleteBraces + enableTypeOverCompletion + } } - var body: some View { - PreferencesContent { - PreferencesSection("Default Tab Width") { - HStack(spacing: 5) { - TextField("", value: $prefs.preferences.textEditing.defaultTabWidth, formatter: tabWidthFormatter) - .multilineTextAlignment(.trailing) - .frame(width: 40) - Stepper( - "Default Tab Width:", - value: $prefs.preferences.textEditing.defaultTabWidth, - in: 1...8 - ) - Text("spaces") - } - } - PreferencesSection("Font") { - fontSelector - } - PreferencesSection("Font Size") { - fontSizeSelector - } - PreferencesSection("Line Height") { - lineHeight - } - PreferencesSection("Code completion") { - autocompleteBraces - enableTypeOverCompletion - } - PreferencesSection("Line Wrapping") { - wrapLinesToEditorWidth - } + private var lineWrappingSection: some View { + PreferencesSection("Line Wrapping") { + wrapLinesToEditorWidth } } + // MARK: - Preference Views + @ViewBuilder private var fontSelector: some View { - Picker("Font:", selection: $prefs.preferences.textEditing.font.customFont) { - Text("System Font") - .tag(false) - Text("Custom") - .tag(true) - } - .fixedSize() - if prefs.preferences.textEditing.font.customFont { - FontPicker( - "\(prefs.preferences.textEditing.font.name) \(prefs.preferences.textEditing.font.size)", - name: $prefs.preferences.textEditing.font.name, size: $prefs.preferences.textEditing.font.size - ) + HStack { + Picker("Font:", selection: $prefs.preferences.textEditing.font.customFont) { + Text("System Font") + .tag(false) + Text("Custom") + .tag(true) + } + .fixedSize() + if prefs.preferences.textEditing.font.customFont { + FontPicker( + "\(prefs.preferences.textEditing.font.name) \(prefs.preferences.textEditing.font.size)", + name: $prefs.preferences.textEditing.font.name, size: $prefs.preferences.textEditing.font.size + ) + } } } @@ -143,4 +132,54 @@ struct TextEditingPreferencesView: View { ) } } + + private var defaultTabWidth: some View { + HStack(spacing: 5) { + TextField("", value: $prefs.preferences.textEditing.defaultTabWidth, formatter: tabWidthFormatter) + .multilineTextAlignment(.trailing) + .frame(width: 40) + Stepper( + "Default Tab Width:", + value: $prefs.preferences.textEditing.defaultTabWidth, + in: 1...8 + ) + Text("spaces") + } + } + + // MARK: - Formatters + + /// Only allows integer values in the range of `[1...8]` + private var tabWidthFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.allowsFloats = false + formatter.minimum = 1 + formatter.maximum = 8 + + return formatter + } + + /// Only allows float values in the range of `[0.75...2.00]` + /// And formats to 2 decimal places. + private var lineHeightFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.allowsFloats = true + formatter.maximumFractionDigits = 2 + formatter.minimumFractionDigits = 2 + formatter.minimum = 0.75 + formatter.maximum = 2.0 + + return formatter + } + + /// Formatter for the font size in the range `[1...288]` + /// Increases by 1 + private var fontSizeFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.allowsFloats = false + formatter.minimum = 1 + formatter.maximum = 288 + + return formatter + } } diff --git a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/EditorThemeView.swift b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/EditorThemeView.swift index 4f455f1987..a71b2720ed 100644 --- a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/EditorThemeView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/EditorThemeView.swift @@ -7,21 +7,33 @@ import SwiftUI +/// A view that implements the `Editor theme` preference section struct EditorThemeView: View { + + // MARK: - View + + var body: some View { + editorThemeViewSection + } + @StateObject private var themeModel: ThemeModel = .shared @StateObject private var prefs: AppPreferencesModel = .shared +} - var body: some View { +private extension EditorThemeView { + + // MARK: - Sections + + private var editorThemeViewSection: some View { ZStack(alignment: .topLeading) { EffectView(.contentBackground) if let selectedTheme = themeModel.selectedTheme, let index = themeModel.themes.firstIndex(of: selectedTheme) { VStack(alignment: .leading, spacing: 0) { - Toggle("Use theme background ", isOn: $prefs.preferences.theme.useThemeBackground) - .padding(.bottom, 20) + useThemeBackground HStack(spacing: 0) { VStack(alignment: .leading, spacing: 10) { PreferencesColorPicker( @@ -37,7 +49,8 @@ struct EditorThemeView: View { label: "Invisibles" ) } - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: 10) { PreferencesColorPicker( $themeModel.themes[index].editor.background.swiftColor, @@ -52,13 +65,11 @@ struct EditorThemeView: View { label: "Selection" ) } - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, alignment: .leading) } - .padding(.bottom, 20) - Text("Syntax") - .font(.system(size: 13, weight: .semibold)) - .foregroundColor(.secondary) - .padding(.bottom, 10) + .padding(.bottom, 20) + + syntaxText HStack(spacing: 0) { VStack(alignment: .leading, spacing: 10) { PreferencesColorPicker( @@ -82,7 +93,8 @@ struct EditorThemeView: View { label: "Variables" ) } - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: 10) { PreferencesColorPicker( $themeModel.themes[index].editor.values.swiftColor, @@ -105,15 +117,33 @@ struct EditorThemeView: View { label: "Comments" ) } - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, alignment: .leading) } + .padding(20) } - .padding(20) } else { - Text("Select a Theme") - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + selectTheme } } } + + // MARK: - Preference Views + + private var useThemeBackground: some View { + Toggle("Use theme background ", isOn: $prefs.preferences.theme.useThemeBackground) + .padding(.bottom, 20) + } + + private var selectTheme: some View { + Text("Select a Theme") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } + + private var syntaxText: some View { + Text("Syntax") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.secondary) + .padding(.bottom, 10) + } } diff --git a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/PreviewThemeView.swift b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/PreviewThemeView.swift index 38486229fb..71bf6dfc6a 100644 --- a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/PreviewThemeView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/PreviewThemeView.swift @@ -7,22 +7,45 @@ import SwiftUI +/// A view that implements the `Preview theme` preference section struct PreviewThemeView: View { + + // MARK: - View + + var body: some View { + previewTheme + } + @StateObject private var themeModel: ThemeModel = .shared +} - var body: some View { +private extension PreviewThemeView { + + // MARK: - Sections + + private var previewTheme: some View { ZStack(alignment: .topLeading) { EffectView(.contentBackground) if themeModel.selectedTheme == nil { - Text("Select a Theme") - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + selectTheme } else { - Text("Preview is not yet implemented") - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + previewNotYetImplemented } } } + + // MARK: - Preferences Views + + private var selectTheme: some View { + Text("Select a Theme") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } + + private var previewNotYetImplemented: some View { + Text("Preview is not yet implemented") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } } diff --git a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/TerminalThemeView.swift b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/TerminalThemeView.swift index 6d481bb4f2..88f750dfd4 100644 --- a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/TerminalThemeView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/TerminalThemeView.swift @@ -7,20 +7,31 @@ import SwiftUI +/// A view that implements the `Terminal theme` preference section struct TerminalThemeView: View { + + // MARK: - View + + var body: some View { + terminalThemeView + } + @StateObject private var prefs: AppPreferencesModel = .shared @StateObject private var themeModel: ThemeModel = .shared +} - var body: some View { +private extension TerminalThemeView { + + // MARK: - Sections + + private var terminalThemeView: some View { ZStack(alignment: .topLeading) { EffectView(.contentBackground) if themeModel.selectedTheme == nil { - Text("Select a Theme") - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + selectTheme } else { VStack(alignment: .leading, spacing: 15) { topToggles @@ -32,6 +43,14 @@ struct TerminalThemeView: View { } } + // MARK: - Preference Views + + private var selectTheme: some View { + Text("Select a Theme") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } + private var topToggles: some View { VStack(alignment: .leading) { Toggle("Always use dark terminal appearance", isOn: $prefs.preferences.terminal.darkAppearance) diff --git a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/ThemePreferencesView.swift b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/ThemePreferencesView.swift index 1e57d7bed6..27dbf670c8 100644 --- a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/ThemePreferencesView.swift @@ -10,6 +10,13 @@ import Preferences /// A view that implements the `Theme` preference section struct ThemePreferencesView: View { + + // MARK: - View + + var body: some View { + themePreferencesSection + } + @Environment(\.colorScheme) var colorScheme @@ -21,36 +28,20 @@ struct ThemePreferencesView: View { @State private var listView: Bool = false +} - var body: some View { +private extension ThemePreferencesView { + + // MARK: - Sections + + private var themePreferencesSection: some View { VStack(spacing: 20) { frame HStack(alignment: .center) { - Toggle( - "Automatically change theme based on system appearance", - isOn: $prefs.preferences.theme.mirrorSystemAppearance - ) - .onChange(of: prefs.preferences.theme.mirrorSystemAppearance) { value in - if value { - if colorScheme == .dark { - themeModel.selectedTheme = themeModel.selectedDarkTheme - } else { - themeModel.selectedTheme = themeModel.selectedLightTheme - } - } else { - themeModel.selectedTheme = themeModel.themes.first { - $0.name == prefs.preferences.theme.selectedTheme - } - } - } - + changeThemeOnSystemAppearance Spacer() - Button("Get More Themes...") {} - .disabled(true) - .help("Not yet implemented") - HelpButton {} - .disabled(true) - .help("Not yet implemented") + getMoreThemes + } } .frame(width: 872) @@ -60,6 +51,8 @@ struct ThemePreferencesView: View { } } + // MARK: - Preference Views + private var frame: some View { VStack(alignment: .leading, spacing: 16) { HStack(spacing: 1) { @@ -72,6 +65,36 @@ struct ThemePreferencesView: View { } } + private var changeThemeOnSystemAppearance: some View { + Toggle( + "Automatically change theme based on system appearance", + isOn: $prefs.preferences.theme.mirrorSystemAppearance + ) + .onChange(of: prefs.preferences.theme.mirrorSystemAppearance) { value in + if value { + if colorScheme == .dark { + themeModel.selectedTheme = themeModel.selectedDarkTheme + } else { + themeModel.selectedTheme = themeModel.selectedLightTheme + } + } else { + themeModel.selectedTheme = themeModel.themes.first { + $0.name == prefs.preferences.theme.selectedTheme + } + } + } + } + + @ViewBuilder + private var getMoreThemes: some View { + Button("Get More Themes...") {} + .disabled(true) + .help("Not yet implemented") + HelpButton {} + .disabled(true) + .help("Not yet implemented") + } + private var sidebar: some View { VStack(spacing: 1) { PreferencesToolbar { diff --git a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/ThemePreviewIcon.swift b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/ThemePreviewIcon.swift index b0ce9219ef..4459291141 100644 --- a/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/ThemePreviewIcon.swift +++ b/CodeEdit/Features/AppPreferences/Sections/ThemePreferences/ThemePreviewIcon.swift @@ -7,7 +7,15 @@ import SwiftUI +/// A view that implements the `Theme Preview` preference section struct ThemePreviewIcon: View { + + // MARK: - View + + var body: some View { + themePreviewIconSection + } + var theme: Theme var colorScheme: ColorScheme @@ -19,12 +27,16 @@ struct ThemePreviewIcon: View { self._selection = selection self.colorScheme = colorScheme } +} - var body: some View { +private extension ThemePreviewIcon { + + // MARK: - Sections + + private var themePreviewIconSection: some View { VStack { ZStack(alignment: .topLeading) { - RoundedRectangle(cornerRadius: 3) - .foregroundColor(Color(hex: colorScheme == .dark ? 0x4c4c4c : 0xbbbbbb)) + circularRectangle HStack(spacing: 1) { sidebar @@ -41,12 +53,7 @@ struct ThemePreviewIcon: View { .strokeBorder(lineWidth: 2) .foregroundColor(selection == theme ? .accentColor : .clear) } - Text(theme.displayName) - .font(.subheadline) - .padding(.horizontal, 7) - .padding(.vertical, 2) - .foregroundColor(selection == theme ? .white : .primary) - .background(Capsule().foregroundColor(selection == theme ? .accentColor : .clear)) + themeDisplayName } .help(theme.metadataDescription) .onTapGesture { @@ -56,6 +63,22 @@ struct ThemePreviewIcon: View { } } + // MARK: - Preference Views + + private var circularRectangle: some View { + RoundedRectangle(cornerRadius: 3) + .foregroundColor(Color(hex: colorScheme == .dark ? 0x4c4c4c : 0xbbbbbb)) + } + + private var themeDisplayName: some View { + Text(theme.displayName) + .font(.subheadline) + .padding(.horizontal, 7) + .padding(.vertical, 2) + .foregroundColor(selection == theme ? .white : .primary) + .background(Capsule().foregroundColor(selection == theme ? .accentColor : .clear)) + } + private var sidebar: some View { ZStack(alignment: .topLeading) { Rectangle() diff --git a/CodeEdit/Page.swift b/CodeEdit/Page.swift new file mode 100644 index 0000000000..d5ba130f37 --- /dev/null +++ b/CodeEdit/Page.swift @@ -0,0 +1,58 @@ +// +// Page.swift +// CodeEdit +// +// Created by Raymond Vleeshouwer on 30/03/23. +// + +import Foundation +import SwiftUI + +/// An enum of all the preferences tabs +enum Name: String { + // MARK: - App Preferences + case appPreferencesSection = "App Preferences" + + case generalPreferences = "General" + case accountPreferences = "Accounts" + case behaviorPreferences = "Behaviors" + case navigationPreferences = "Navigation" + case themePreferences = "Themes" + case textEditingPreferences = "Text Editing" + case terminalPreferences = "Terminal" + case keybindingsPreferences = "Key Bindings" + case sourceControlPreferences = "Source Control" + case componentsPreferences = "Components" + case locationPreferences = "Locations" + case advancedPreferences = "Advanced" +} + +/// A struct for a preferences tab +struct Page: Hashable, Identifiable { + /// Default intializer + internal init(_ name: Name, icon: Icon? = nil, children: [Page] = []) { + self.children = children + self.name = name + // If no icon is found, will default to a red question mark + self.icon = icon ?? .init(baseColor: .red, systemName: "questionmark.app", icon: .system("questionmark.app")) + } + + var id: String { name.rawValue } + + let name: Name + // Optional because some Pages do not have any children + let children: [Page]? + var nameString: LocalizedStringKey { LocalizedStringKey(name.rawValue) } + let icon: Icon + + /// A struct for a sidebar icon, with a base color and SF Symbol + struct Icon: Hashable { + enum IconResource: Equatable, Hashable { + case system(_ name: String) + } + + let baseColor: Color + let systemName: String + let icon: IconResource + } +} diff --git a/CodeEdit/VenturaColors.swift b/CodeEdit/VenturaColors.swift new file mode 100644 index 0000000000..e1ae6ca337 --- /dev/null +++ b/CodeEdit/VenturaColors.swift @@ -0,0 +1,40 @@ +// +// VenturaColors.swift +// CodeEdit +// +// Created by Raymond Vleeshouwer on 31/03/23. +// + +import SwiftUI + +struct Colors { + let gray: Color = Color.init( + hex: "#B4B4B9", + alpha: 1.0 + ) + + let blue: Color = Color.init( + hex: "#5A9FF8", + alpha: 1.0 + ) + + let orange: Color = Color.init( + hex: "#EC695C", + alpha: 1.0 + ) + + let pink: Color = Color.init( + hex: "#ED6C85", + alpha: 1.0 + ) + + let green: Color = Color.init( + hex: "#82E37C", + alpha: 1.0 + ) + + let purple: Color = Color.init( + hex: "#8483EF", + alpha: 1.0 + ) +} diff --git a/CodeEdit/VenturaPreferences.swift b/CodeEdit/VenturaPreferences.swift new file mode 100644 index 0000000000..44e758ab1f --- /dev/null +++ b/CodeEdit/VenturaPreferences.swift @@ -0,0 +1,222 @@ +// +// VenturaPreferences.swift +// CodeEdit +// +// Created by Raymond Vleeshouwer on 26/03/23. +// + +import SwiftUI +import CodeEditSymbols +import AppKit + +/// A struct for a Ventura-style preference +struct VenturaPreferences: View { + + /// An array of navigationItem(s) + private static let pages: [Page] = [ + .init(.appPreferencesSection, children: [ + .init( + .generalPreferences, + icon: .init( + baseColor: Colors().gray, + systemName: "gear", + icon: .system("gear") + ) + ), + .init( + .accountPreferences, + icon: .init( + baseColor: Colors().blue, + systemName: "at", + icon: .system("at") + ) + ), + .init( + .behaviorPreferences, + icon: .init( + baseColor: Colors().orange, + systemName: "flowchart", + icon: .system("flowchart") + ) + ), + .init( + .navigationPreferences, + icon: .init( + baseColor: Colors().green, + systemName: "arrow.triangle.turn.up.right.diamond", + icon: .system("arrow.triangle.turn.up.right.diamond") + ) + ), + .init( + .themePreferences, + icon: .init( + baseColor: Colors().pink, + systemName: "paintbrush", + icon: .system("paintbrush") + ) + ), + .init( + .textEditingPreferences, + icon: .init( + baseColor: Colors().blue, + systemName: "square.and.pencil", + icon: .system("square.and.pencil") + ) + ), + .init( + .terminalPreferences, + icon: .init( + baseColor: Colors().blue, + systemName: "terminal", + icon: .system("terminal") + ) + ), + .init( + .keybindingsPreferences, + icon: .init( + baseColor: Colors().gray, + systemName: "keyboard", + icon: .system("keyboard") + ) + ), + .init( + .sourceControlPreferences, + icon: .init( + baseColor: Colors().blue, + systemName: "arrow.triangle.pull", + icon: .system("arrow.triangle.pull") + ) + ), + .init( + .componentsPreferences, + icon: .init( + baseColor: Colors().blue, + systemName: "puzzlepiece", + icon: .system("puzzlepiece") + ) + ), + .init( + .locationPreferences, + icon: .init( + baseColor: Colors().green, + systemName: "externaldrive", + icon: .system("externaldrive") + ) + ), + .init( + .advancedPreferences, + icon: .init( + baseColor: Colors().gray, + systemName: "gearshape.2", + icon: .system("gearshape.2") + ) + ) + ]) + ] + + /// Variables for the selected Page, the current search text and software updater + @State private var selectedPage = pages.first?.children?.first + @State private var searchText: String = "" + @State private var hidden: Bool = true + let updater: SoftwareUpdater + + @ViewBuilder + /// Generates a NavigationItem from a Page + private func navigationItem(item: Page) -> some View { + if searchText.isEmpty || item.name.rawValue.lowercased().contains(searchText.lowercased()) { + NavigationLink(value: item) { + Label { + Text(item.nameString) + .font(.system(size: 12)) + .padding(.leading, 15) + } icon: { + if let icon = item.icon { + Group { + Image(systemName: item.icon.systemName) + } + .foregroundColor(.primary) + .frame(width: 20, height: 20) + .background( + RoundedRectangle( + cornerRadius: 5, + style: .continuous + ) + .fill(icon.baseColor.gradient) + ) + .padding(.leading, 20) + } else { + EmptyView() + } + } + } + } + } + + var body: some View { + NavigationSplitView { + VStack { + List(Self.pages, selection: $selectedPage) { category in + Section(category.nameString) { + if category.children != nil { + // Can force un-wrap because we checked if it was nil + ForEach(category.children!) { child in + navigationItem(item: child) + } + } + } + } + .navigationSplitViewColumnWidth(215) + } + } detail: { + ScrollView { + // TODO: Align left + Text(selectedPage?.nameString ?? "No selection") + .font(.headline) + .fontWeight(.bold) + Group { + if selectedPage?.name != nil { + // Can force un-wrap because we just checked if it was nil + switch selectedPage!.name { + case .generalPreferences: + GeneralPreferencesView() + .environmentObject(updater) + .navigationTitle("General") + case .themePreferences: + ThemePreferencesView() // TODO: i think this is broken when i did the refactor, try to find what's wrong + // this might be a problem with fixed .frame() sizes + .navigationTitle("Themes") + case .textEditingPreferences: + TextEditingPreferencesView() + .navigationTitle("Text Editing") + case .terminalPreferences: + TerminalPreferencesView() + .navigationTitle("Terminal") + case .sourceControlPreferences: + SourceControlPreferencesView() + .navigationTitle("Source Control") + .navigationSubtitle("subtitle") + case .locationPreferences: + LocationsPreferencesView() + .navigationTitle("Locations") + default: + Text("Implementation Needed") + .frame(alignment: .center) + } + } + } + .padding(20) + } + .navigationSplitViewColumnWidth(715) + } .task { + print("run") + NSApp.windows.second?.toolbarStyle = .unifiedCompact + NSApp.windows.second?.titleVisibility = NSWindow.TitleVisibility.hidden + NSApp.windows.second?.titlebarAppearsTransparent = true + print(NSApp.windows.second?.title ?? "no title") + } + // TODO: Make window resizable + .searchable(text: $searchText, placement: .sidebar, prompt: "Search") + .frame(width: 1200, height: 750) + .opacity(hidden ? 1 : 0) + } +} diff --git a/Documentation.docc/AppPreferences/Create a View.md b/Documentation.docc/AppPreferences/Create a View.md index 1b571006c7..81e7d9bd17 100644 --- a/Documentation.docc/AppPreferences/Create a View.md +++ b/Documentation.docc/AppPreferences/Create a View.md @@ -11,38 +11,66 @@ Now let's take a look at the ``GeneralPreferencesView``. ```swift import SwiftUI -public struct PreferencesGeneralView: View { +struct GeneralPreferencesView: View { - @StateObject - private var prefs: AppPreferencesModel = .shared + // MARK: - View - public init() {} + init() {} - public var body: some View { + var body: some View { PreferencesContent { - PreferencesSection("Appearance") { - // ... - } - PreferencesSection("File Icon Style") { - // ... - } - PreferencesSection("Reopen Behavior") { - // ... - } + appearanceSection + showIssuesSection + fileExtensionsSection } } + + @StateObject + private var prefs: AppPreferencesModel = .shared } ``` As you can see ``AppPreferencesModel`` is already setup and ready to use. -Note that in order to align all the options in a nice and uniform way we wrap them into a -``PreferencesContent`` and individual ``PreferencesSection`` views. To add your option toggle below the other options just add something like this: ```swift -PreferencesSection("Reopen Behavior") { - Toggle("Your new Option", value: $prefs.preferences.general.yourNewOption) +private extension GeneralPreferencesView { + + // MARK: - Sections + + private var yourOptionSection: some View { + PreferencesSection("Your Option") { + yourOption + } + } + + // MARK: - Preferences View + + private var yourOption: some View { + Toggle("Your text", isOn: $prefs.preferences.general.yourNewOption) + } +} +``` + +Then add it to `var body: some View` + +```swift +struct GeneralPreferencesView: View { + + // MARK: - View + + init() {} + + var body: some View { + PreferencesContent { + appearanceSection + showIssuesSection + fileExtensionsSection + // REMOVEME: et cetera + yourOptionSection + } + } } ``` @@ -50,33 +78,96 @@ And now you're done! ## Implement new Section +> Tip: Rename YourSection to the section name that you want + To implement a new section first create a new folder inside the `Sections` folder and name it accordingly. -In this example we might want to name it "AdvancedPreferences". +Inside the folder create a new SwiftUI view and name it "PreferencesYourSectionView.swift". + +Then find `VenturaPreferences.swift` by searching in the filter field at the bottom of the file explorer, then create a new page for your view like so: -Inside the folder create a new SwiftUI view and name it "PreferencesAdvancedView.swift". +> Tip: The order that pages are arranged in the array is the same as in the settings window, the first array member will be the top item -Then go to the main `CodeEdit` target and open the "AppDelegate.swift" and scroll down to find the `private lazy var preferencesWindowController` instance. Search for the `Pane` titled "Advanced" and replace `PreferencesPlaceholderView()` with your newly created `PreferencesAdvancedView()`. +```swift +private static let pages: [Page] = [ + .init(.appPreferencesSection, children: [ + .init( + .generalPreferences, + icon: .init( + baseColor: Colors().gray, + systemName: "gear", + icon: .system("gear") + ) + ), + .init( + .yourSection, + icon: .init( + baseColor: Colors().yourColor + systemName: "// REMOVEME: Find an SF Symbol that is similar to the icon you imagined" + icon: .system("// REMOVEME: Find an SF Symbol that is similar to the icon you imagined") + ) + ) + ] +] +``` -Back in "PreferencesAdvancedView.swift" implement your option like that: +Then find the file `Page.swift` and add `YourSection` to the `enum Name` like this: + +```swift +enum Name: String { + case appPreferencesSection = "App Preferences" + + case generalPreferences = "General" + case advancedPreferences = "Advanced" + // REMOVEME: et cetera + case yourSection = "YourSection" +} +``` + +Back in `YourSectionView.swift` implement your option like this: ```swift import SwiftUI -public struct PreferencesAdvancedView: View { +struct YourSectionPreferencesView: View { + var body: some View { + yourToggleSection + } @StateObject private var prefs: AppPreferencesModel = .shared public init() {} +} - public var body: some View { - PreferencesContent { - PreferencesSection("Reopen Behavior") { - Toggle("Your new Option", value: $prefs.preferences.general.yourNewOption) - } - } +private extension YourSectionPreferencesView { + + // MARK: - Sections + + private var yourToggleSection: some View { + yourToggle + } + + // MARK: - Preferences Views + + private var yourToggle: some View { + Toggle("Your option", isOn: $prefs.preferences.general.yourNewOption) } } ``` +When you are done, add `YourSectionView` to `VenturaPreferences.swift`: + +```swift +if selectedPage?.name != nil { + // Can force un-wrap because we just checked if it was nil + switch selectedPage!.name { + case .generalPreferences: + GeneralPreferencesView() + .environmentObject(updater) + case .themePreferences: + ThemePreferencesView() + // REMOVEME: et cetera + case .yourSection: + YourSectionPreferencesView() +```