From ec3fd26ef2696bdf758fd3315be0a0dff8f41936 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Wed, 30 Mar 2022 14:02:56 +0200 Subject: [PATCH 01/26] started themes settings UI --- .../src/HelperViews/HelpButton.swift | 29 +++ .../src/HelperViews/ThemePreviewIcon.swift | 244 ++++++++++++++++++ .../src/PreferencesThemeView.swift | 175 ++++++++++++- 3 files changed, 437 insertions(+), 11 deletions(-) create mode 100644 CodeEditModules/Modules/AppPreferences/src/HelperViews/HelpButton.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift diff --git a/CodeEditModules/Modules/AppPreferences/src/HelperViews/HelpButton.swift b/CodeEditModules/Modules/AppPreferences/src/HelperViews/HelpButton.swift new file mode 100644 index 0000000000..0186ef1a10 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/HelperViews/HelpButton.swift @@ -0,0 +1,29 @@ +// +// HelpButton.swift +// +// +// Created by Lukas Pistrol on 30.03.22. +// + +import SwiftUI + +struct HelpButton: View { + var action : () -> Void + + var body: some View { + Button(action: action, label: { + ZStack { + Circle() + .strokeBorder(Color(NSColor.separatorColor), lineWidth: 0.5) + .background(Circle().foregroundColor(Color(NSColor.controlColor))) + .shadow(color: Color(NSColor.separatorColor).opacity(0.3), radius: 0.5) + .shadow(color: Color(NSColor.shadowColor).opacity(0.3), radius: 1, y: 0.5) + .frame(width: 20, height: 20) +// Text("?").font(.system(size: 15, weight: .medium )) + Image(systemName: "questionmark") + .font(.system(size: 12.5, weight: .medium)) + } + }) + .buttonStyle(PlainButtonStyle()) + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift b/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift new file mode 100644 index 0000000000..d2fc82a348 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift @@ -0,0 +1,244 @@ +// +// ThemePreviewIcon.swift +// +// +// Created by Lukas Pistrol on 30.03.22. +// + +import SwiftUI + +@available(macOS 12, *) +struct ThemePreviewIcon: View { + @Environment(\.colorScheme) + private var colorScheme + + var body: some View { + ZStack(alignment: .topLeading) { + RoundedRectangle(cornerRadius: 3) + .foregroundColor(Color(hex: colorScheme == .dark ? 0x4c4c4c : 0xbbbbbb)) + + HStack(spacing: 1) { + sidebar + content + } + .clipShape(RoundedRectangle(cornerRadius: 2)) + .padding(1) + } + .frame(width: 128, height: 86) + } + + private var sidebar: some View { + ZStack(alignment: .topLeading) { + Rectangle() + .foregroundColor(Color(hex: colorScheme == .dark ? 0x383838 : 0xd0d0d0)) + .frame(width: 36) + + HStack(spacing: 1.5) { + Circle().foregroundColor(.red) + Circle().foregroundColor(Color(hex: 0xf9b82d)) + Circle().foregroundColor(.green) + } + .frame(width: 12, height: 3) + .padding(4) + } + } + + private var content: some View { + VStack(spacing: 0) { + Rectangle() + .foregroundColor(Color(hex: colorScheme == .dark ? 0x2b2b2b : 0xe0e0e0)) + .frame(height: 10) + Rectangle() + .foregroundColor(Color(hex: 0x1f2029)) + .overlay(alignment: .topLeading) { + codeWindow + } + } + } + + private var codeWindow: some View { + VStack(alignment: .leading, spacing: 4) { + block1 + block2 + block3 + block4 + block5 + } + .padding(.top, 6) + .padding(.leading, 6) + } + + private var block1: some View { + codeStatement(0x97be71, length: 25) + } + + private var block2: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 1) { + codeStatement(0xef8bb6, length: 6) + codeStatement(0x70c1e2, length: 6) + codeStatement(0xffffff, length: 1) + codeStatement(0xffffff, length: 1) + codeStatement(0x70c1e2, length: 8) + codeStatement(0xffffff, length: 1) + codeStatement(0x70c1e2, length: 8) + codeStatement(0xffffff, length: 1) + codeStatement(0xef8bb6, length: 6) + codeStatement(0xf0907f, length: 7) + } + HStack(spacing: 1) { + codeStatement(0xef8bb6, length: 6) + codeStatement(0x70c1e2, length: 8) + codeStatement(0xef8bb6, length: 6) + codeStatement(0xf0907f, length: 12) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeStatement(0xef8bb6, length: 6) + codeStatement(0xf0907f, length: 14) + codeStatement(0xffffff, length: 1) + } + } + } + + private var block3: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 1) { + codeStatement(0xef8bb6, length: 4) + codeStatement(0x70c1e2, length: 8) + codeStatement(0xffffff, length: 1) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(3) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(5) + codeStatement(0xffffff, length: 3) + codeStatement(0xd6c775, length: 1) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(5) + codeStatement(0xffffff, length: 6) + codeStatement(0xf0907f, length: 7) + codeStatement(0xf0907f, length: 5) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(5) + codeStatement(0xffffff, length: 5) + codeStatement(0xef8bb6, length: 5) + } + HStack(spacing: 1) { + codeSpace(3) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeStatement(0xffffff, length: 2) + } + } + } + + private var block4: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 1) { + codeStatement(0xef8bb6, length: 6) + codeStatement(0xef8bb6, length: 7) + codeStatement(0xc6a3f9, length: 8) + codeStatement(0x70c1e2, length: 3) + codeStatement(0xffffff, length: 2) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(3) + codeStatement(0xef8bb6, length: 4) + codeStatement(0xffffff, length: 1) + codeStatement(0x70c1e2, length: 5) + codeStatement(0xffffff, length: 1) + codeStatement(0x70c1e2, length: 8) + codeStatement(0xffffff, length: 1) + codeStatement(0xffffff, length: 1) + codeStatement(0x93c7bc, length: 8) + codeStatement(0xffffff, length: 2) + } + } + } + + private var block5: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 1) { + codeSpace(3) + codeStatement(0xef8bb6, length: 4) + codeStatement(0x70c1e2, length: 10) + codeStatement(0xffffff, length: 1) + codeStatement(0x93c7bc, length: 11) + codeStatement(0xffffff, length: 3) + codeStatement(0xef8bb6, length: 2) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(5) + codeStatement(0x93c7bc, length: 8) + codeStatement(0xffffff, length: 2) + codeStatement(0x70c1e2, length: 5) + codeStatement(0xffffff, length: 1) + codeStatement(0xef8bb6, length: 2) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(7) + codeStatement(0xef8bb6, length: 3) + codeStatement(0x70c1e2, length: 12) + codeStatement(0xffffff, length: 1) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(9) + codeStatement(0xffffff, length: 3) + codeStatement(0x93c7bc, length: 5) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(9) + codeStatement(0xffffff, length: 1) + } + HStack(spacing: 1) { + codeSpace(9) + codeStatement(0xffffff, length: 3) + codeStatement(0x93c7bc, length: 5) + codeStatement(0xffffff, length: 1) + codeStatement(0x93c7bc, length: 6) + codeStatement(0xffffff, length: 1) + codeStatement(0xd6c775, length: 1) + codeStatement(0xffffff, length: 1) + } + } + } + + private func codeStatement(_ color: Int, length: Double) -> some View { + Rectangle() + .foregroundColor(Color(hex: color)) + .frame(width: length, height: 2) + } + + private func codeSpace(_ length: Double) -> some View { + Rectangle() + .foregroundColor(.clear) + .frame(width: length-1, height: 2) + } +} + +@available(macOS 12, *) +struct ThemePreviewIcon_Previews: PreviewProvider { + static var previews: some View { + ThemePreviewIcon() + .background(Color.white) + .preferredColorScheme(.light) + + ThemePreviewIcon() + .background(Color.white) + .preferredColorScheme(.dark) + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift index 8313a0a0f0..46ed802da7 100644 --- a/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift @@ -11,6 +11,7 @@ import SwiftUI import Preferences import TerminalEmulator +@available(macOS 12, *) public struct PreferencesThemeView: View { @AppStorage(CodeFileView.Theme.storageKey) @@ -37,20 +38,126 @@ public struct PreferencesThemeView: View { public init() {} public var body: some View { - TabView { - editor - .padding() - .tabItem { - Text("Editor") + VStack(spacing: 20) { + newEditor + HStack(alignment: .center) { + Toggle("Automatically change theme based on system appearance", isOn: .constant(true)) + Spacer() + Button("Get More Themes...") {} + HelpButton {} + } + } + .frame(width: 812) + .padding() + } + + private var newEditor: some View { + VStack(alignment: .leading, spacing: 16) { + HStack(spacing: 1) { + sidebar + settingsContent + } + .padding(1) + .background(Rectangle().foregroundColor(Color(NSColor.separatorColor))) + .frame(height: 468) + } + } + + private var sidebar: some View { + VStack(spacing: 1) { + toolbar { + CustomSegmentedControl(.constant(0), + options: [ + "Dark Mode", + "Light Mode" + ]) + } + ScrollView { + let grid: [GridItem] = .init( + repeating: .init(.fixed(128), spacing: 20, alignment: .center), + count: 2 + ) + LazyVGrid(columns: grid, + alignment: .center, + spacing: 20) { + ForEach(0..<10) { i in + VStack { + ThemePreviewIcon() + Text("Civic") + } + } } - terminal - .padding() - .tabItem { - Text("Terminal") + .padding(.vertical, 20) + } + .background(Color(NSColor.controlBackgroundColor)) + toolbar { + sidebarBottomToolbar + } + .frame(height: 27) + } + .frame(width: 320) + } + + private var sidebarBottomToolbar: some View { + HStack { + Button {} label: { + Image(systemName: "plus") + } + .buttonStyle(.plain) + Button {} label: { + Image(systemName: "minus") + } + .buttonStyle(.plain) + Spacer() + Button {} label: { + Image(systemName: "list.dash") + } + .buttonStyle(.plain) + Button {} label: { + Image(systemName: "square.grid.2x2") + } + .buttonStyle(.plain) + } + } + + private var settingsContent: some View { + VStack(spacing: 1) { + toolbar { + CustomSegmentedControl(.constant(1), + options: [ + "Preview", + "Editor", + "Terminal" + ]) + } + Rectangle() + .foregroundColor(Color(NSColor.controlBackgroundColor)) + toolbar { + HStack { + Spacer() + Button {} label: { + Image(systemName: "info.circle") + } + .buttonStyle(.plain) } + } } - .frame(width: 844) - .padding(30) + } + + private func toolbar( + height: Double = 27, + bgColor: Color = Color(NSColor.controlBackgroundColor), + @ViewBuilder content: @escaping () -> T + ) -> some View { + ZStack { + Rectangle() + .foregroundColor(bgColor) + HStack { + content() + .padding(.horizontal, 8) + } + } + .frame(height: height) } private var editor: some View { @@ -149,3 +256,49 @@ public struct PreferencesThemeView: View { .labelsHidden() } } + +@available(macOS 12, *) +struct PrefsThemes_Previews: PreviewProvider { + static var previews: some View { + PreferencesThemeView() + .preferredColorScheme(.light) + } +} + +@available(macOS 12, *) +struct CustomSegmentedControl: View { + + init(_ selection: Binding, options: [String]) { + self._preselectedIndex = selection + self.options = options + } + + @Binding var preselectedIndex: Int + var options: [String] + let color = Color.accentColor + var body: some View { + HStack(spacing: 0) { + ForEach(options.indices, id: \.self) { index in + Text(options[index]) + .font(.subheadline) + .foregroundColor(preselectedIndex == index ? .white : .primary) + .frame(height: 16) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .background { + Rectangle() + .fill(color) + .cornerRadius(5) + .padding(2) + .opacity(preselectedIndex == index ? 1 : 0.01) + } + .onTapGesture { + withAnimation(.interactiveSpring()) { + preselectedIndex = index + } + } + } + } + .frame(height: 20) + } +} From dd2129bf5fc99245f7ca702c7eec7103b74702a8 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Wed, 30 Mar 2022 16:50:43 +0200 Subject: [PATCH 02/26] added selection state for themes --- .../src/HelperViews/ThemePreviewIcon.swift | 49 +++++++++++++++---- .../src/PreferencesThemeView.swift | 19 ++++--- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift b/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift index d2fc82a348..c8b6448a46 100644 --- a/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift +++ b/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift @@ -12,19 +12,48 @@ struct ThemePreviewIcon: View { @Environment(\.colorScheme) private var colorScheme + init(_ id: Int, selection: Binding) { + self.id = id + self._selection = selection + } + + var id: Int + + @Binding + var selection: Int + var body: some View { - ZStack(alignment: .topLeading) { - RoundedRectangle(cornerRadius: 3) - .foregroundColor(Color(hex: colorScheme == .dark ? 0x4c4c4c : 0xbbbbbb)) + VStack { + ZStack(alignment: .topLeading) { + RoundedRectangle(cornerRadius: 3) + .foregroundColor(Color(hex: colorScheme == .dark ? 0x4c4c4c : 0xbbbbbb)) - HStack(spacing: 1) { - sidebar - content + HStack(spacing: 1) { + sidebar + content + } + .clipShape(RoundedRectangle(cornerRadius: 2)) + .padding(1) } - .clipShape(RoundedRectangle(cornerRadius: 2)) .padding(1) + .frame(width: 130, height: 88) + .overlay { + RoundedRectangle(cornerRadius: 4) + .strokeBorder(lineWidth: 2) + .foregroundColor(selection == id ? .accentColor : .clear) + } + Text("Civic") + .font(.subheadline) + .padding(.horizontal, 7) + .padding(.vertical, 2) + .foregroundColor(selection == id ? .white : .primary) + .background(Capsule().foregroundColor(selection == id ? .accentColor : .clear)) + } + .onTapGesture { + withAnimation(.interactiveSpring()) { + self.selection = id + } } - .frame(width: 128, height: 86) } private var sidebar: some View { @@ -233,11 +262,11 @@ struct ThemePreviewIcon: View { @available(macOS 12, *) struct ThemePreviewIcon_Previews: PreviewProvider { static var previews: some View { - ThemePreviewIcon() + ThemePreviewIcon(0, selection: .constant(0)) .background(Color.white) .preferredColorScheme(.light) - ThemePreviewIcon() + ThemePreviewIcon(0, selection: .constant(1)) .background(Color.white) .preferredColorScheme(.dark) } diff --git a/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift index 46ed802da7..671153b57d 100644 --- a/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift @@ -13,6 +13,11 @@ import TerminalEmulator @available(macOS 12, *) public struct PreferencesThemeView: View { + @Environment(\.colorScheme) + var colorScheme + + @AppStorage(Appearances.storageKey) + private var appearance: Appearances = .default @AppStorage(CodeFileView.Theme.storageKey) var editorTheme: CodeFileView.Theme = .atelierSavannaAuto @@ -47,7 +52,7 @@ public struct PreferencesThemeView: View { HelpButton {} } } - .frame(width: 812) + .frame(width: 872) .padding() } @@ -63,6 +68,9 @@ public struct PreferencesThemeView: View { } } + @State + private var selectedTheme: Int = 0 + private var sidebar: some View { VStack(spacing: 1) { toolbar { @@ -74,17 +82,14 @@ public struct PreferencesThemeView: View { } ScrollView { let grid: [GridItem] = .init( - repeating: .init(.fixed(128), spacing: 20, alignment: .center), + repeating: .init(.fixed(130), spacing: 20, alignment: .center), count: 2 ) LazyVGrid(columns: grid, alignment: .center, spacing: 20) { - ForEach(0..<10) { i in - VStack { - ThemePreviewIcon() - Text("Civic") - } + ForEach(0..<10) { id in + ThemePreviewIcon(id, selection: $selectedTheme) } } .padding(.vertical, 20) From 871540bed668f3d06f11fc6209aa7ce546a03ee8 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 31 Mar 2022 00:52:19 +0200 Subject: [PATCH 03/26] Added shadows, added color picker dummies --- .../src/HelperViews/ThemePreviewIcon.swift | 1 + .../src/PreferencesThemeView.swift | 58 ++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift b/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift index c8b6448a46..f8e0a7d81e 100644 --- a/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift +++ b/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift @@ -37,6 +37,7 @@ struct ThemePreviewIcon: View { } .padding(1) .frame(width: 130, height: 88) + .shadow(color: Color(NSColor.shadowColor).opacity(0.1), radius: 8, x: 0, y: 2) .overlay { RoundedRectangle(cornerRadius: 4) .strokeBorder(lineWidth: 2) diff --git a/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift index 671153b57d..628917a1b1 100644 --- a/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift @@ -11,6 +11,7 @@ import SwiftUI import Preferences import TerminalEmulator +// swiftlint:disable all @available(macOS 12, *) public struct PreferencesThemeView: View { @Environment(\.colorScheme) @@ -135,8 +136,53 @@ public struct PreferencesThemeView: View { "Terminal" ]) } - Rectangle() - .foregroundColor(Color(NSColor.controlBackgroundColor)) + ZStack(alignment: .topLeading) { + Rectangle() + .foregroundColor(Color(NSColor.controlBackgroundColor)) + VStack(alignment: .leading, spacing: 0) { + Toggle("Use theme background ", isOn: .constant(true)) + .padding(.bottom, 20) + HStack(spacing: 0) { + VStack(alignment: .leading, spacing: 10) { + colorPicker(.constant(Color(hex: 0xffffff)), label: "Text") + colorPicker(.constant(Color(hex: 0xffffff)), label: "Cursor") + colorPicker(.constant(Color(hex: 0x636363)), label: "Invisibles") + } + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: 10) { + colorPicker(.constant(Color(hex: 0x1e1e1e)), label: "Background") + colorPicker(.constant(Color(hex: 0x303030)), label: "Current Line") + colorPicker(.constant(Color(hex: 0x8b8b8b)), label: "Selection") + } + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding(.bottom, 20) + Text("Syntax") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.secondary) + .padding(.bottom, 10) + HStack(spacing: 0) { + VStack(alignment: .leading, spacing: 10) { + colorPicker(.constant(Color(hex: 0xef8bb6)), label: "Keywords") + colorPicker(.constant(Color(hex: 0xc6a3f9)), label: "Commands") + colorPicker(.constant(Color(hex: 0x93c7bc)), label: "Types") + colorPicker(.constant(Color(hex: 0x93c7bc)), label: "Attributes") + colorPicker(.constant(Color(hex: 0x70c1e2)), label: "Variables") + } + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: 10) { + colorPicker(.constant(Color(hex: 0x70c1e2)), label: "Values") + colorPicker(.constant(Color(hex: 0xd6c775)), label: "Numbers") + colorPicker(.constant(Color(hex: 0xf0907f)), label: "Strings") + colorPicker(.constant(Color(hex: 0x93c7bc)), label: "Characters") + colorPicker(.constant(Color(hex: 0x97be71)), label: "Comments") + } + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .padding(20) + } + toolbar { HStack { Spacer() @@ -256,6 +302,14 @@ public struct PreferencesThemeView: View { } } + private func colorPicker(_ color: Binding, label: String) -> some View { + HStack { + ColorPicker(selection: color, supportsOpacity: false) { } + .labelsHidden() + Text(label) + } + } + private func ansiColorPicker(_ color: Binding) -> some View { ColorPicker(selection: color, supportsOpacity: false) { } .labelsHidden() From 5c340b04b49a211c642f37f90122fa9831131ac9 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Thu, 31 Mar 2022 14:25:11 +0200 Subject: [PATCH 04/26] Implementing theme files (json). Implementing new UI for theme and terminal settings --- CodeEdit.xcodeproj/project.pbxproj | 12 + CodeEdit/AppDelegate.swift | 2 +- CodeEdit/DefaultThemes/default-dark.json | 58 +++ .../HelperViews/PreferencesColorPicker.swift | 31 ++ .../src/HelperViews/SegmentedControl.swift | 44 +++ .../src/HelperViews/ThemePreviewIcon.swift | 274 ------------- .../src/PreferencesThemeView.swift | 363 ------------------ .../ThemePreferences/EditorThemeView.swift | 88 +++++ .../src/ThemePreferences/Model/Theme.swift | 124 ++++++ .../ThemePreferences/Model/ThemeModel.swift | 88 +++++ .../ThemePreferences/TerminalThemeView.swift | 149 +++++++ .../ThemePreferencesView.swift | 167 ++++++++ .../ThemePreferences/ThemePreviewIcon.swift | 272 +++++++++++++ .../FontPicker/src/FontPickerView.swift | 3 +- .../src/Model/AnsiColors.swift | 20 +- CodeEditModules/Package.swift | 3 +- 16 files changed, 1054 insertions(+), 644 deletions(-) create mode 100644 CodeEdit/DefaultThemes/default-dark.json create mode 100644 CodeEditModules/Modules/AppPreferences/src/HelperViews/PreferencesColorPicker.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/HelperViews/SegmentedControl.swift delete mode 100644 CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift delete mode 100644 CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index a7952ef33d..d8825520a4 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 0485EB2327E7791400138301 /* QuickOpenPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB2227E7791400138301 /* QuickOpenPreviewView.swift */; }; 0485EB2527E7B9C800138301 /* Overlays in Frameworks */ = {isa = PBXBuildFile; productRef = 0485EB2427E7B9C800138301 /* Overlays */; }; 2803257127F3CF1F009C7DC2 /* AppPreferences in Frameworks */ = {isa = PBXBuildFile; productRef = 2803257027F3CF1F009C7DC2 /* AppPreferences */; }; + 28069DA427F5BCF70016BC47 /* default-dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 28069DA327F5BCDD0016BC47 /* default-dark.json */; }; 2806CBD427EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2806CBD327EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift */; }; 2813F93827ECC4AA00E305E4 /* FindNavigatorResultList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B327E9989900CB86D0 /* FindNavigatorResultList.swift */; }; 2813F93927ECC4C300E305E4 /* NavigatorSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776E627E3413200D46668 /* NavigatorSidebar.swift */; }; @@ -85,6 +86,7 @@ 0485EB1C27E7338100138301 /* QuickOpenItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickOpenItem.swift; sourceTree = ""; }; 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceCodeFileView.swift; sourceTree = ""; }; 0485EB2227E7791400138301 /* QuickOpenPreviewView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenPreviewView.swift; sourceTree = ""; tabWidth = 4; }; + 28069DA327F5BCDD0016BC47 /* default-dark.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "default-dark.json"; sourceTree = ""; }; 2806CBD327EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorContextMenu.swift; sourceTree = ""; }; 286471AA27ED51FD0039369D /* ProjectNavigator.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = ProjectNavigator.swift; sourceTree = ""; tabWidth = 4; }; 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = BreadcrumbsComponent.swift; sourceTree = ""; tabWidth = 4; }; @@ -177,6 +179,14 @@ path = "Quick Open"; sourceTree = ""; }; + 28069DA527F5BD320016BC47 /* DefaultThemes */ = { + isa = PBXGroup; + children = ( + 28069DA327F5BCDD0016BC47 /* default-dark.json */, + ); + path = DefaultThemes; + sourceTree = ""; + }; 286471AC27ED52950039369D /* ProjectNavigator */ = { isa = PBXGroup; children = ( @@ -274,6 +284,7 @@ 043C321927E32295006AE443 /* MainMenu.xib */, B673FDAC27E8296A00795864 /* PressActionsModifier.swift */, B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */, + 28069DA527F5BD320016BC47 /* DefaultThemes */, ); path = CodeEdit; sourceTree = ""; @@ -447,6 +458,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 28069DA427F5BCF70016BC47 /* default-dark.json in Resources */, B658FB3727DA9E1000EA4DBD /* Preview Assets.xcassets in Resources */, D7211D4727E06BFE008F2ED7 /* Localizable.strings in Resources */, B658FB3427DA9E1000EA4DBD /* Assets.xcassets in Resources */, diff --git a/CodeEdit/AppDelegate.swift b/CodeEdit/AppDelegate.swift index 8e88c0f82e..00ebf9bf96 100644 --- a/CodeEdit/AppDelegate.swift +++ b/CodeEdit/AppDelegate.swift @@ -169,7 +169,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { title: "Themes", toolbarIcon: NSImage(systemSymbolName: "paintbrush", accessibilityDescription: nil)! ) { - PreferencesThemeView() + ThemePreferencesView() }, Preferences.Pane( identifier: Preferences.PaneIdentifier("TextEditing"), diff --git a/CodeEdit/DefaultThemes/default-dark.json b/CodeEdit/DefaultThemes/default-dark.json new file mode 100644 index 0000000000..4261c74bd5 --- /dev/null +++ b/CodeEdit/DefaultThemes/default-dark.json @@ -0,0 +1,58 @@ +{ + "strings" : { + "color" : "#f0907f" + }, + "comments" : { + "color" : "#97be71" + }, + "numbers" : { + "color" : "#d6c775" + }, + "metadata" : { + "darkTheme" : true, + "license" : "MIT", + "author" : "CodeEdit", + "description" : "CodeEdit default dark theme.", + "name" : "Default (Dark)", + "distributionURL" : "https:\/\/github.com\/CodeEditApp\/CodeEdit" + }, + "commands" : { + "color" : "#c6a3f9" + }, + "lineHighlight" : { + "color" : "#303030" + }, + "values" : { + "color" : "#70c1e2" + }, + "background" : { + "color" : "#1e1e1e" + }, + "keywords" : { + "color" : "#ef8bb6" + }, + "text" : { + "color" : "#DDDDDD" + }, + "insertionPoint" : { + "color" : "#DDDDDD" + }, + "selection" : { + "color" : "#8b8b8b" + }, + "types" : { + "color" : "#93c7bc" + }, + "variables" : { + "color" : "#70c1e2" + }, + "attributes" : { + "color" : "#93c7bc" + }, + "characters" : { + "color" : "#93c7bc" + }, + "invisibles" : { + "color" : "#636363" + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/HelperViews/PreferencesColorPicker.swift b/CodeEditModules/Modules/AppPreferences/src/HelperViews/PreferencesColorPicker.swift new file mode 100644 index 0000000000..bfd537c1d5 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/HelperViews/PreferencesColorPicker.swift @@ -0,0 +1,31 @@ +// +// PreferencesColorPicker.swift +// +// +// Created by Lukas Pistrol on 31.03.22. +// + +import SwiftUI + +struct PreferencesColorPicker: View { + + @Binding + var color: Color + + private var label: String? + + init(_ color: Binding, label: String? = nil) { + self._color = color + self.label = label + } + + var body: some View { + HStack { + ColorPicker(selection: $color, supportsOpacity: false) { } + .labelsHidden() + if let label = label { + Text(label) + } + } + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/HelperViews/SegmentedControl.swift b/CodeEditModules/Modules/AppPreferences/src/HelperViews/SegmentedControl.swift new file mode 100644 index 0000000000..982d3592ba --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/HelperViews/SegmentedControl.swift @@ -0,0 +1,44 @@ +// +// SegmentedControl.swift +// +// +// Created by Lukas Pistrol on 31.03.22. +// + +import SwiftUI + +@available(macOS 12, *) +struct SegmentedControl: View { + + init(_ selection: Binding, options: [String]) { + self._preselectedIndex = selection + self.options = options + } + + @Binding var preselectedIndex: Int + var options: [String] + let color = Color.accentColor + var body: some View { + HStack(spacing: 0) { + ForEach(options.indices, id: \.self) { index in + Text(options[index]) + .font(.subheadline) + .foregroundColor(preselectedIndex == index ? .white : .primary) + .frame(height: 16) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .background { + Rectangle() + .fill(color) + .cornerRadius(5) + .padding(2) + .opacity(preselectedIndex == index ? 1 : 0.01) + } + .onTapGesture { + preselectedIndex = index + } + } + } + .frame(height: 20) + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift b/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift deleted file mode 100644 index f8e0a7d81e..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/HelperViews/ThemePreviewIcon.swift +++ /dev/null @@ -1,274 +0,0 @@ -// -// ThemePreviewIcon.swift -// -// -// Created by Lukas Pistrol on 30.03.22. -// - -import SwiftUI - -@available(macOS 12, *) -struct ThemePreviewIcon: View { - @Environment(\.colorScheme) - private var colorScheme - - init(_ id: Int, selection: Binding) { - self.id = id - self._selection = selection - } - - var id: Int - - @Binding - var selection: Int - - var body: some View { - VStack { - ZStack(alignment: .topLeading) { - RoundedRectangle(cornerRadius: 3) - .foregroundColor(Color(hex: colorScheme == .dark ? 0x4c4c4c : 0xbbbbbb)) - - HStack(spacing: 1) { - sidebar - content - } - .clipShape(RoundedRectangle(cornerRadius: 2)) - .padding(1) - } - .padding(1) - .frame(width: 130, height: 88) - .shadow(color: Color(NSColor.shadowColor).opacity(0.1), radius: 8, x: 0, y: 2) - .overlay { - RoundedRectangle(cornerRadius: 4) - .strokeBorder(lineWidth: 2) - .foregroundColor(selection == id ? .accentColor : .clear) - } - Text("Civic") - .font(.subheadline) - .padding(.horizontal, 7) - .padding(.vertical, 2) - .foregroundColor(selection == id ? .white : .primary) - .background(Capsule().foregroundColor(selection == id ? .accentColor : .clear)) - } - .onTapGesture { - withAnimation(.interactiveSpring()) { - self.selection = id - } - } - } - - private var sidebar: some View { - ZStack(alignment: .topLeading) { - Rectangle() - .foregroundColor(Color(hex: colorScheme == .dark ? 0x383838 : 0xd0d0d0)) - .frame(width: 36) - - HStack(spacing: 1.5) { - Circle().foregroundColor(.red) - Circle().foregroundColor(Color(hex: 0xf9b82d)) - Circle().foregroundColor(.green) - } - .frame(width: 12, height: 3) - .padding(4) - } - } - - private var content: some View { - VStack(spacing: 0) { - Rectangle() - .foregroundColor(Color(hex: colorScheme == .dark ? 0x2b2b2b : 0xe0e0e0)) - .frame(height: 10) - Rectangle() - .foregroundColor(Color(hex: 0x1f2029)) - .overlay(alignment: .topLeading) { - codeWindow - } - } - } - - private var codeWindow: some View { - VStack(alignment: .leading, spacing: 4) { - block1 - block2 - block3 - block4 - block5 - } - .padding(.top, 6) - .padding(.leading, 6) - } - - private var block1: some View { - codeStatement(0x97be71, length: 25) - } - - private var block2: some View { - VStack(alignment: .leading, spacing: 1) { - HStack(spacing: 1) { - codeStatement(0xef8bb6, length: 6) - codeStatement(0x70c1e2, length: 6) - codeStatement(0xffffff, length: 1) - codeStatement(0xffffff, length: 1) - codeStatement(0x70c1e2, length: 8) - codeStatement(0xffffff, length: 1) - codeStatement(0x70c1e2, length: 8) - codeStatement(0xffffff, length: 1) - codeStatement(0xef8bb6, length: 6) - codeStatement(0xf0907f, length: 7) - } - HStack(spacing: 1) { - codeStatement(0xef8bb6, length: 6) - codeStatement(0x70c1e2, length: 8) - codeStatement(0xef8bb6, length: 6) - codeStatement(0xf0907f, length: 12) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeStatement(0xef8bb6, length: 6) - codeStatement(0xf0907f, length: 14) - codeStatement(0xffffff, length: 1) - } - } - } - - private var block3: some View { - VStack(alignment: .leading, spacing: 1) { - HStack(spacing: 1) { - codeStatement(0xef8bb6, length: 4) - codeStatement(0x70c1e2, length: 8) - codeStatement(0xffffff, length: 1) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(3) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(5) - codeStatement(0xffffff, length: 3) - codeStatement(0xd6c775, length: 1) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(5) - codeStatement(0xffffff, length: 6) - codeStatement(0xf0907f, length: 7) - codeStatement(0xf0907f, length: 5) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(5) - codeStatement(0xffffff, length: 5) - codeStatement(0xef8bb6, length: 5) - } - HStack(spacing: 1) { - codeSpace(3) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeStatement(0xffffff, length: 2) - } - } - } - - private var block4: some View { - VStack(alignment: .leading, spacing: 1) { - HStack(spacing: 1) { - codeStatement(0xef8bb6, length: 6) - codeStatement(0xef8bb6, length: 7) - codeStatement(0xc6a3f9, length: 8) - codeStatement(0x70c1e2, length: 3) - codeStatement(0xffffff, length: 2) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(3) - codeStatement(0xef8bb6, length: 4) - codeStatement(0xffffff, length: 1) - codeStatement(0x70c1e2, length: 5) - codeStatement(0xffffff, length: 1) - codeStatement(0x70c1e2, length: 8) - codeStatement(0xffffff, length: 1) - codeStatement(0xffffff, length: 1) - codeStatement(0x93c7bc, length: 8) - codeStatement(0xffffff, length: 2) - } - } - } - - private var block5: some View { - VStack(alignment: .leading, spacing: 1) { - HStack(spacing: 1) { - codeSpace(3) - codeStatement(0xef8bb6, length: 4) - codeStatement(0x70c1e2, length: 10) - codeStatement(0xffffff, length: 1) - codeStatement(0x93c7bc, length: 11) - codeStatement(0xffffff, length: 3) - codeStatement(0xef8bb6, length: 2) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(5) - codeStatement(0x93c7bc, length: 8) - codeStatement(0xffffff, length: 2) - codeStatement(0x70c1e2, length: 5) - codeStatement(0xffffff, length: 1) - codeStatement(0xef8bb6, length: 2) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(7) - codeStatement(0xef8bb6, length: 3) - codeStatement(0x70c1e2, length: 12) - codeStatement(0xffffff, length: 1) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(9) - codeStatement(0xffffff, length: 3) - codeStatement(0x93c7bc, length: 5) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(9) - codeStatement(0xffffff, length: 1) - } - HStack(spacing: 1) { - codeSpace(9) - codeStatement(0xffffff, length: 3) - codeStatement(0x93c7bc, length: 5) - codeStatement(0xffffff, length: 1) - codeStatement(0x93c7bc, length: 6) - codeStatement(0xffffff, length: 1) - codeStatement(0xd6c775, length: 1) - codeStatement(0xffffff, length: 1) - } - } - } - - private func codeStatement(_ color: Int, length: Double) -> some View { - Rectangle() - .foregroundColor(Color(hex: color)) - .frame(width: length, height: 2) - } - - private func codeSpace(_ length: Double) -> some View { - Rectangle() - .foregroundColor(.clear) - .frame(width: length-1, height: 2) - } -} - -@available(macOS 12, *) -struct ThemePreviewIcon_Previews: PreviewProvider { - static var previews: some View { - ThemePreviewIcon(0, selection: .constant(0)) - .background(Color.white) - .preferredColorScheme(.light) - - ThemePreviewIcon(0, selection: .constant(1)) - .background(Color.white) - .preferredColorScheme(.dark) - } -} diff --git a/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift deleted file mode 100644 index 628917a1b1..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/PreferencesThemeView.swift +++ /dev/null @@ -1,363 +0,0 @@ -// -// File.swift -// -// -// Created by Lukas Pistrol on 30.03.22. -// - -import CodeFile -import FontPicker -import SwiftUI -import Preferences -import TerminalEmulator - -// swiftlint:disable all -@available(macOS 12, *) -public struct PreferencesThemeView: View { - @Environment(\.colorScheme) - var colorScheme - - @AppStorage(Appearances.storageKey) - private var appearance: Appearances = .default - - @AppStorage(CodeFileView.Theme.storageKey) - var editorTheme: CodeFileView.Theme = .atelierSavannaAuto - - @AppStorage(TerminalShellType.storageKey) - var shellType: TerminalShellType = .default - - @AppStorage(TerminalFont.storageKey) - var terminalFontSelection: TerminalFont = .default - - @AppStorage(TerminalFontName.storageKey) - var terminalFontName: String = TerminalFontName.default - - @AppStorage(TerminalFontSize.storageKey) - var terminalFontSize: Int = TerminalFontSize.default - - @AppStorage(TerminalColorScheme.storageKey) - var terminalColorSchmeme: TerminalColorScheme = .default - - @StateObject - private var colors = AnsiColors.shared - - public init() {} - - public var body: some View { - VStack(spacing: 20) { - newEditor - HStack(alignment: .center) { - Toggle("Automatically change theme based on system appearance", isOn: .constant(true)) - Spacer() - Button("Get More Themes...") {} - HelpButton {} - } - } - .frame(width: 872) - .padding() - } - - private var newEditor: some View { - VStack(alignment: .leading, spacing: 16) { - HStack(spacing: 1) { - sidebar - settingsContent - } - .padding(1) - .background(Rectangle().foregroundColor(Color(NSColor.separatorColor))) - .frame(height: 468) - } - } - - @State - private var selectedTheme: Int = 0 - - private var sidebar: some View { - VStack(spacing: 1) { - toolbar { - CustomSegmentedControl(.constant(0), - options: [ - "Dark Mode", - "Light Mode" - ]) - } - ScrollView { - let grid: [GridItem] = .init( - repeating: .init(.fixed(130), spacing: 20, alignment: .center), - count: 2 - ) - LazyVGrid(columns: grid, - alignment: .center, - spacing: 20) { - ForEach(0..<10) { id in - ThemePreviewIcon(id, selection: $selectedTheme) - } - } - .padding(.vertical, 20) - } - .background(Color(NSColor.controlBackgroundColor)) - toolbar { - sidebarBottomToolbar - } - .frame(height: 27) - } - .frame(width: 320) - } - - private var sidebarBottomToolbar: some View { - HStack { - Button {} label: { - Image(systemName: "plus") - } - .buttonStyle(.plain) - Button {} label: { - Image(systemName: "minus") - } - .buttonStyle(.plain) - Spacer() - Button {} label: { - Image(systemName: "list.dash") - } - .buttonStyle(.plain) - Button {} label: { - Image(systemName: "square.grid.2x2") - } - .buttonStyle(.plain) - } - } - - private var settingsContent: some View { - VStack(spacing: 1) { - toolbar { - CustomSegmentedControl(.constant(1), - options: [ - "Preview", - "Editor", - "Terminal" - ]) - } - ZStack(alignment: .topLeading) { - Rectangle() - .foregroundColor(Color(NSColor.controlBackgroundColor)) - VStack(alignment: .leading, spacing: 0) { - Toggle("Use theme background ", isOn: .constant(true)) - .padding(.bottom, 20) - HStack(spacing: 0) { - VStack(alignment: .leading, spacing: 10) { - colorPicker(.constant(Color(hex: 0xffffff)), label: "Text") - colorPicker(.constant(Color(hex: 0xffffff)), label: "Cursor") - colorPicker(.constant(Color(hex: 0x636363)), label: "Invisibles") - } - .frame(maxWidth: .infinity, alignment: .leading) - VStack(alignment: .leading, spacing: 10) { - colorPicker(.constant(Color(hex: 0x1e1e1e)), label: "Background") - colorPicker(.constant(Color(hex: 0x303030)), label: "Current Line") - colorPicker(.constant(Color(hex: 0x8b8b8b)), label: "Selection") - } - .frame(maxWidth: .infinity, alignment: .leading) - } - .padding(.bottom, 20) - Text("Syntax") - .font(.system(size: 13, weight: .semibold)) - .foregroundColor(.secondary) - .padding(.bottom, 10) - HStack(spacing: 0) { - VStack(alignment: .leading, spacing: 10) { - colorPicker(.constant(Color(hex: 0xef8bb6)), label: "Keywords") - colorPicker(.constant(Color(hex: 0xc6a3f9)), label: "Commands") - colorPicker(.constant(Color(hex: 0x93c7bc)), label: "Types") - colorPicker(.constant(Color(hex: 0x93c7bc)), label: "Attributes") - colorPicker(.constant(Color(hex: 0x70c1e2)), label: "Variables") - } - .frame(maxWidth: .infinity, alignment: .leading) - VStack(alignment: .leading, spacing: 10) { - colorPicker(.constant(Color(hex: 0x70c1e2)), label: "Values") - colorPicker(.constant(Color(hex: 0xd6c775)), label: "Numbers") - colorPicker(.constant(Color(hex: 0xf0907f)), label: "Strings") - colorPicker(.constant(Color(hex: 0x93c7bc)), label: "Characters") - colorPicker(.constant(Color(hex: 0x97be71)), label: "Comments") - } - .frame(maxWidth: .infinity, alignment: .leading) - } - } - .padding(20) - } - - toolbar { - HStack { - Spacer() - Button {} label: { - Image(systemName: "info.circle") - } - .buttonStyle(.plain) - } - } - } - } - - private func toolbar( - height: Double = 27, - bgColor: Color = Color(NSColor.controlBackgroundColor), - @ViewBuilder content: @escaping () -> T - ) -> some View { - ZStack { - Rectangle() - .foregroundColor(bgColor) - HStack { - content() - .padding(.horizontal, 8) - } - } - .frame(height: height) - } - - private var editor: some View { - Form { - Picker("Editor Theme", selection: $editorTheme) { - Text("Atelier Savanna (Auto)") - .tag(CodeFileView.Theme.atelierSavannaAuto) - Text("Atelier Savanna Dark") - .tag(CodeFileView.Theme.atelierSavannaDark) - Text("Atelier Savanna Light") - .tag(CodeFileView.Theme.atelierSavannaLight) - Text("Agate") - .tag(CodeFileView.Theme.agate) - Text("Ocean") - .tag(CodeFileView.Theme.ocean) - } - .fixedSize() - } - } - - private var terminal: some View { - Form { - Picker("Terminal Shell", selection: $shellType) { - Text("System Default") - .tag(TerminalShellType.auto) - Divider() - Text("ZSH") - .tag(TerminalShellType.zsh) - Text("Bash") - .tag(TerminalShellType.bash) - } - .fixedSize() - - Picker("Terminal Appearance", selection: $terminalColorSchmeme) { - Text("App Default") - .tag(TerminalColorScheme.auto) - Divider() - Text("Light") - .tag(TerminalColorScheme.light) - Text("Dark") - .tag(TerminalColorScheme.dark) - } - .fixedSize() - - Picker("Terminal Font", selection: $terminalFontSelection) { - Text("System Font") - .tag(TerminalFont.systemFont) - Divider() - Text("Custom") - .tag(TerminalFont.custom) - } - .fixedSize() - if terminalFontSelection == .custom { - FontPicker( - "\(terminalFontName) \(terminalFontSize)", - name: $terminalFontName, - size: $terminalFontSize - ) - } - Divider() - .frame(maxWidth: 400) - Section { - VStack(alignment: .leading, spacing: 0) { - HStack(spacing: 0) { - ansiColorPicker($colors.colors[0]) - ansiColorPicker($colors.colors[1]) - ansiColorPicker($colors.colors[2]) - ansiColorPicker($colors.colors[3]) - ansiColorPicker($colors.colors[4]) - ansiColorPicker($colors.colors[5]) - ansiColorPicker($colors.colors[6]) - ansiColorPicker($colors.colors[7]) - Text("Normal").padding(.leading, 4) - } - HStack(spacing: 0) { - ansiColorPicker($colors.colors[8]) - ansiColorPicker($colors.colors[9]) - ansiColorPicker($colors.colors[10]) - ansiColorPicker($colors.colors[11]) - ansiColorPicker($colors.colors[12]) - ansiColorPicker($colors.colors[13]) - ansiColorPicker($colors.colors[14]) - ansiColorPicker($colors.colors[15]) - Text("Bright").padding(.leading, 4) - } - } - } - Button("Restore Defaults") { - AnsiColors.shared.resetDefault() - } - } - } - - private func colorPicker(_ color: Binding, label: String) -> some View { - HStack { - ColorPicker(selection: color, supportsOpacity: false) { } - .labelsHidden() - Text(label) - } - } - - private func ansiColorPicker(_ color: Binding) -> some View { - ColorPicker(selection: color, supportsOpacity: false) { } - .labelsHidden() - } -} - -@available(macOS 12, *) -struct PrefsThemes_Previews: PreviewProvider { - static var previews: some View { - PreferencesThemeView() - .preferredColorScheme(.light) - } -} - -@available(macOS 12, *) -struct CustomSegmentedControl: View { - - init(_ selection: Binding, options: [String]) { - self._preselectedIndex = selection - self.options = options - } - - @Binding var preselectedIndex: Int - var options: [String] - let color = Color.accentColor - var body: some View { - HStack(spacing: 0) { - ForEach(options.indices, id: \.self) { index in - Text(options[index]) - .font(.subheadline) - .foregroundColor(preselectedIndex == index ? .white : .primary) - .frame(height: 16) - .padding(.horizontal, 8) - .padding(.vertical, 3) - .background { - Rectangle() - .fill(color) - .cornerRadius(5) - .padding(2) - .opacity(preselectedIndex == index ? 1 : 0.01) - } - .onTapGesture { - withAnimation(.interactiveSpring()) { - preselectedIndex = index - } - } - } - } - .frame(height: 20) - } -} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift new file mode 100644 index 0000000000..8498c46bff --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift @@ -0,0 +1,88 @@ +// +// EditorThemeView.swift +// +// +// Created by Lukas Pistrol on 31.03.22. +// + +import SwiftUI + +struct EditorThemeView: View { + + @StateObject + private var themeModel: ThemeModel = .shared + + var body: some View { + ZStack(alignment: .topLeading) { + Rectangle() + .foregroundColor(Color(NSColor.controlBackgroundColor)) + if let selectedTheme = themeModel.selectedTheme, + let index = themeModel.themes.firstIndex(of: selectedTheme) { + VStack(alignment: .leading, spacing: 0) { + Toggle("Use theme background ", isOn: .constant(true)) + .padding(.bottom, 20) + HStack(spacing: 0) { + VStack(alignment: .leading, spacing: 10) { + PreferencesColorPicker($themeModel.themes[index].text.swiftColor, + label: "Text") + PreferencesColorPicker($themeModel.themes[index].insertionPoint.swiftColor, + label: "Cursor") + PreferencesColorPicker($themeModel.themes[index].invisibles.swiftColor, + label: "Invisibles") + } + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: 10) { + PreferencesColorPicker($themeModel.themes[index].background.swiftColor, + label: "Background") + PreferencesColorPicker($themeModel.themes[index].lineHighlight.swiftColor, + label: "Current Line") + PreferencesColorPicker($themeModel.themes[index].selection.swiftColor, + label: "Selection") + } + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding(.bottom, 20) + Text("Syntax") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.secondary) + .padding(.bottom, 10) + HStack(spacing: 0) { + VStack(alignment: .leading, spacing: 10) { + PreferencesColorPicker($themeModel.themes[index].keywords.swiftColor, + label: "Keywords") + PreferencesColorPicker($themeModel.themes[index].commands.swiftColor, + label: "Commands") + PreferencesColorPicker($themeModel.themes[index].types.swiftColor, + label: "Types") + PreferencesColorPicker($themeModel.themes[index].attributes.swiftColor, + label: "Attributes") + PreferencesColorPicker($themeModel.themes[index].variables.swiftColor, + label: "Variables") + } + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: 10) { + PreferencesColorPicker($themeModel.themes[index].values.swiftColor, + label: "Values") + PreferencesColorPicker($themeModel.themes[index].numbers.swiftColor, + label: "Numbers") + PreferencesColorPicker($themeModel.themes[index].strings.swiftColor, + label: "Strings") + PreferencesColorPicker($themeModel.themes[index].characters.swiftColor, + label: "Characters") + PreferencesColorPicker($themeModel.themes[index].comments.swiftColor, + label: "Comments") + } + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .padding(20) + } + } + } +} + +struct EditorThemeView_Previews: PreviewProvider { + static var previews: some View { + EditorThemeView() + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift new file mode 100644 index 0000000000..90699bfacd --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -0,0 +1,124 @@ +// +// File.swift +// +// +// Created by Lukas Pistrol on 31.03.22. +// + +import SwiftUI + +// MARK: - Theme +public struct Theme: Identifiable, Codable, Equatable { + + public static func == (lhs: Theme, rhs: Theme) -> Bool { + lhs.id == rhs.id + } + + public var id: String { metadata.name } + + public var text: Attributes + public var insertionPoint: Attributes + public var invisibles: Attributes + public var background: Attributes + public var lineHighlight: Attributes + public var selection: Attributes + public var keywords: Attributes + public var commands: Attributes + public var types: Attributes + public var attributes: Attributes + public var variables: Attributes + public var values: Attributes + public var numbers: Attributes + public var strings: Attributes + public var characters: Attributes + public var comments: Attributes + public var metadata: Metadata + + public init( + text: Attributes, + insertionPoint: Attributes, + invisibles: Attributes, + background: Attributes, + lineHighlight: Attributes, + selection: Attributes, + keywords: Attributes, + commands: Attributes, + types: Attributes, + attributes: Attributes, + variables: Attributes, + values: Attributes, + numbers: Attributes, + strings: Attributes, + characters: Attributes, + comments: Attributes, + metadata: Metadata + ) { + self.text = text + self.insertionPoint = insertionPoint + self.invisibles = invisibles + self.background = background + self.lineHighlight = lineHighlight + self.selection = selection + self.keywords = keywords + self.commands = commands + self.types = types + self.attributes = attributes + self.variables = variables + self.values = values + self.numbers = numbers + self.strings = strings + self.characters = characters + self.comments = comments + self.metadata = metadata + } +} + +// MARK: - Attributes +public struct Attributes: Codable { + public var color: String + + public init(color: String) { + self.color = color + } + + public internal(set) var swiftColor: Color { + get { + Color(hex: color) + } + set { + self.color = newValue.hexString + } + } +} + +// MARK: - Metadata +public struct Metadata: Codable { + public var author, license, metadataDescription: String + public var distributionUrl: String + public var name: String + public var darkTheme: Bool + + enum CodingKeys: String, CodingKey { + case author, license + case metadataDescription = "description" + case distributionUrl = "distributionURL" + case name + case darkTheme + } + + public init( + author: String, + license: String, + metadataDescription: String, + distributionUrl: String, + name: String, + darkTheme: Bool + ) { + self.author = author + self.license = license + self.metadataDescription = metadataDescription + self.distributionUrl = distributionUrl + self.name = name + self.darkTheme = darkTheme + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift new file mode 100644 index 0000000000..4383698bd4 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -0,0 +1,88 @@ +// +// File.swift +// +// +// Created by Lukas Pistrol on 31.03.22. +// + +import SwiftUI + +public class ThemeModel: ObservableObject { + + public static let shared: ThemeModel = .init() + + @Published + var selectedAppearance: Int = 0 + + @Published + var selectedTab: Int = 2 + + @Published + public var themes: [Theme] = [] { + didSet { try? saveThemes() } + } + + @Published + public var selectedTheme: Theme? + + public var darkThemes: [Theme] { + themes.filter { $0.metadata.darkTheme } + } + + public var lightThemes: [Theme] { + themes.filter { !$0.metadata.darkTheme } + } + + init() { + try? loadThemes() + self.selectedTheme = themes.first + } + + private func load(from url: URL) throws { + let json = try Data(contentsOf: url) + let theme = try JSONDecoder().decode(Theme.self, from: json) + self.themes.append(theme) + } + + public func loadThemes() throws { + themes.removeAll() + let url = baseURL.appendingPathComponent("themes") + + var isDir: ObjCBool = false + + if !filemanager.fileExists(atPath: url.path, isDirectory: &isDir) { + try filemanager.createDirectory(at: url, withIntermediateDirectories: false) + } + + let content = try filemanager.contentsOfDirectory(atPath: url.path).filter { $0.contains(".json") } + if content.isEmpty { + guard let defaultUrl = Bundle.main.url(forResource: "default-dark", withExtension: "json") else { + return + } + try load(from: defaultUrl) + return + } + try content.forEach { file in + let fileURL = url.appendingPathComponent(file) + try load(from: fileURL) + } + } + + private func saveThemes() throws { + let url = baseURL.appendingPathComponent("themes") + try themes.forEach { theme in + let data = try JSONEncoder().encode(theme) + let json = try JSONSerialization.jsonObject(with: data) + let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) + try prettyJSON.write( + to: url.appendingPathComponent(theme.metadata.name).appendingPathExtension("json"), + options: .atomic + ) + } + } + + public let filemanager = FileManager.default + public var baseURL: URL { + filemanager.homeDirectoryForCurrentUser.appendingPathComponent(".codeedit") + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift new file mode 100644 index 0000000000..faacd1d11d --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift @@ -0,0 +1,149 @@ +// +// TerminalThemeView.swift +// +// +// Created by Lukas Pistrol on 31.03.22. +// + +import SwiftUI +import TerminalEmulator +import FontPicker + +struct TerminalThemeView: View { + @AppStorage(TerminalShellType.storageKey) + var shellType: TerminalShellType = .default + + @AppStorage(TerminalFont.storageKey) + var terminalFontSelection: TerminalFont = .custom + + @AppStorage(TerminalFontName.storageKey) + var terminalFontName: String = TerminalFontName.default + + @AppStorage(TerminalFontSize.storageKey) + var terminalFontSize: Int = TerminalFontSize.default + + @AppStorage(TerminalColorScheme.storageKey) + var terminalColorSchmeme: TerminalColorScheme = .default + + @StateObject + private var colors = AnsiColors.shared + + @State private var darkTerminal: Bool = false + + init() { + self._darkTerminal = .init(initialValue: terminalColorSchmeme == .dark) + } + + var body: some View { + ZStack(alignment: .topLeading) { + Rectangle() + .foregroundColor(Color(NSColor.controlBackgroundColor)) + VStack(alignment: .leading, spacing: 14) { + topToggles + shellSelector + fontSelector + Divider() + ansiColorSelector + } + .padding(20) + } + .onChange(of: darkTerminal) { newValue in + if newValue { + terminalColorSchmeme = .dark + } else { + terminalColorSchmeme = .auto + } + } + } + + private var topToggles: some View { + VStack(alignment: .leading) { + Toggle("Use dark terminal appearance", isOn: $darkTerminal) + } + } + + private var shellSelector: some View { + VStack(alignment: .leading, spacing: 5) { + Text("Terminal Shell") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.secondary) + .padding(.bottom, 5) + Picker("Terminal Shell", selection: $shellType) { + Text("System Default") + .tag(TerminalShellType.auto) + Divider() + Text("ZSH") + .tag(TerminalShellType.zsh) + Text("Bash") + .tag(TerminalShellType.bash) + } + .fixedSize() + .labelsHidden() + } + } + + private var fontSelector: some View { + VStack(alignment: .leading, spacing: 5) { + Text("Font") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.secondary) + .padding(.bottom, 5) + HStack { + Picker("Terminal Font", selection: $terminalFontSelection) { + Text("System Font") + .tag(TerminalFont.systemFont) + Text("Custom") + .tag(TerminalFont.custom) + } + .fixedSize() + .labelsHidden() + if terminalFontSelection == .custom { + FontPicker("\(terminalFontName) \(terminalFontSize)", + name: $terminalFontName, size: $terminalFontSize) + } + } + } + } + + private var ansiColorSelector: some View { + VStack(alignment: .leading, spacing: 5) { + Text("ANSI Colors") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.secondary) + .padding(.bottom, 5) + HStack(spacing: 5) { + PreferencesColorPicker($colors.colors[0]) + PreferencesColorPicker($colors.colors[1]) + PreferencesColorPicker($colors.colors[2]) + PreferencesColorPicker($colors.colors[3]) + PreferencesColorPicker($colors.colors[4]) + PreferencesColorPicker($colors.colors[5]) + PreferencesColorPicker($colors.colors[6]) + PreferencesColorPicker($colors.colors[7]) + Text("Normal").padding(.leading, 4) + } + HStack(spacing: 5) { + PreferencesColorPicker($colors.colors[8]) + PreferencesColorPicker($colors.colors[9]) + PreferencesColorPicker($colors.colors[10]) + PreferencesColorPicker($colors.colors[11]) + PreferencesColorPicker($colors.colors[12]) + PreferencesColorPicker($colors.colors[13]) + PreferencesColorPicker($colors.colors[14]) + PreferencesColorPicker($colors.colors[15]) + Text("Bright").padding(.leading, 4) + } + Button("Restore Defaults") { + AnsiColors.shared.resetDefault() + } + .padding(.top, 5) + } + } +} + +struct TerminalThemeView_Previews: PreviewProvider { + static var previews: some View { + TerminalThemeView() + .preferredColorScheme(.dark) + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift new file mode 100644 index 0000000000..494592d5ed --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -0,0 +1,167 @@ +// +// File.swift +// +// +// Created by Lukas Pistrol on 30.03.22. +// + +import CodeFile +import FontPicker +import SwiftUI +import Preferences +import TerminalEmulator + +// swiftlint:disable all +@available(macOS 12, *) +public struct ThemePreferencesView: View { + @Environment(\.colorScheme) + var colorScheme + + @ObservedObject + private var themeModel: ThemeModel = .shared + + public init() {} + + public var body: some View { + VStack(spacing: 20) { + frame + HStack(alignment: .center) { + Toggle("Automatically change theme based on system appearance", isOn: .constant(true)) + Spacer() + Button("Get More Themes...") {} + HelpButton {} + } + } + .frame(width: 872) + .padding() + .onAppear { + try? themeModel.loadThemes() + } + } + + private var frame: some View { + VStack(alignment: .leading, spacing: 16) { + HStack(spacing: 1) { + sidebar + settingsContent + } + .padding(1) + .background(Rectangle().foregroundColor(Color(NSColor.separatorColor))) + .frame(height: 468) + } + } + + private var sidebar: some View { + VStack(spacing: 1) { + toolbar { + let options = [ + "Dark Mode", + "Light Mode" + ] + SegmentedControl($themeModel.selectedAppearance, options: options) + } + ScrollView { + let grid: [GridItem] = .init( + repeating: .init(.fixed(130), spacing: 20, alignment: .center), + count: 2 + ) + LazyVGrid(columns: grid, + alignment: .center, + spacing: 20) { + ForEach(themeModel.selectedAppearance == 0 ? themeModel.darkThemes : themeModel.lightThemes) { theme in + ThemePreviewIcon(theme, selection: $themeModel.selectedTheme) + .transition(.opacity) + } + } + .padding(.vertical, 20) + } + .background(Color(NSColor.controlBackgroundColor)) + toolbar { + sidebarBottomToolbar + } + .frame(height: 27) + } + .frame(width: 320) + } + + private var sidebarBottomToolbar: some View { + HStack { + Button {} label: { + Image(systemName: "plus") + } + .buttonStyle(.plain) + Button {} label: { + Image(systemName: "minus") + } + .buttonStyle(.plain) + Divider() + Button { try? themeModel.loadThemes() } label: { + Image(systemName: "arrow.clockwise") + } + .buttonStyle(.plain) + Spacer() + Button {} label: { + Image(systemName: "list.dash") + } + .buttonStyle(.plain) + Button {} label: { + Image(systemName: "square.grid.2x2") + } + .buttonStyle(.plain) + } + } + + private var settingsContent: some View { + VStack(spacing: 1) { + let options = [ + "Preview", + "Editor", + "Terminal" + ] + toolbar { + SegmentedControl($themeModel.selectedTab, options: options) + } + switch themeModel.selectedTab { + case 1: + EditorThemeView() + case 2: + TerminalThemeView() + default: + PreviewThemeView() + } + toolbar { + HStack { + Spacer() + Button {} label: { + Image(systemName: "info.circle") + } + .buttonStyle(.plain) + } + } + } + } + + private func toolbar( + height: Double = 27, + bgColor: Color = Color(NSColor.controlBackgroundColor), + @ViewBuilder content: @escaping () -> T + ) -> some View { + ZStack { + Rectangle() + .foregroundColor(bgColor) + HStack { + content() + .padding(.horizontal, 8) + } + } + .frame(height: height) + } +} + +@available(macOS 12, *) +struct PrefsThemes_Previews: PreviewProvider { + static var previews: some View { + ThemePreferencesView() + .preferredColorScheme(.light) + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift new file mode 100644 index 0000000000..5eaa55c0c9 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift @@ -0,0 +1,272 @@ +// +// ThemePreviewIcon.swift +// +// +// Created by Lukas Pistrol on 30.03.22. +// + +import SwiftUI + +@available(macOS 12, *) +struct ThemePreviewIcon: View { + @Environment(\.colorScheme) + private var colorScheme + + init(_ theme: Theme, selection: Binding) { + self.theme = theme + self._selection = selection + } + + var theme: Theme + + @Binding + var selection: Theme? + + var body: some View { + VStack { + ZStack(alignment: .topLeading) { + RoundedRectangle(cornerRadius: 3) + .foregroundColor(Color(hex: colorScheme == .dark ? 0x4c4c4c : 0xbbbbbb)) + + HStack(spacing: 1) { + sidebar + content + } + .clipShape(RoundedRectangle(cornerRadius: 2)) + .padding(1) + } + .padding(1) + .frame(width: 130, height: 88) + .shadow(color: Color(NSColor.shadowColor).opacity(0.1), radius: 8, x: 0, y: 2) + .overlay { + RoundedRectangle(cornerRadius: 4) + .strokeBorder(lineWidth: 2) + .foregroundColor(selection == theme ? .accentColor : .clear) + } + Text(theme.metadata.name) + .font(.subheadline) + .padding(.horizontal, 7) + .padding(.vertical, 2) + .foregroundColor(selection == theme ? .white : .primary) + .background(Capsule().foregroundColor(selection == theme ? .accentColor : .clear)) + } + .onTapGesture { + withAnimation(.interactiveSpring()) { + self.selection = theme + } + } + } + + private var sidebar: some View { + ZStack(alignment: .topLeading) { + Rectangle() + .foregroundColor(Color(hex: colorScheme == .dark ? 0x383838 : 0xd0d0d0)) + .frame(width: 36) + + HStack(spacing: 1.5) { + Circle().foregroundColor(.red) + Circle().foregroundColor(Color(hex: 0xf9b82d)) + Circle().foregroundColor(.green) + } + .frame(width: 12, height: 3) + .padding(4) + } + } + + private var content: some View { + VStack(spacing: 0) { + Rectangle() + .foregroundColor(Color(hex: colorScheme == .dark ? 0x2b2b2b : 0xe0e0e0)) + .frame(height: 10) + Rectangle() + .foregroundColor(theme.background.swiftColor) + .overlay(alignment: .topLeading) { + codeWindow + } + } + } + + private var codeWindow: some View { + VStack(alignment: .leading, spacing: 4) { + block1 + block2 + block3 + block4 + block5 + } + .padding(.top, 6) + .padding(.leading, 6) + } + + private var block1: some View { + codeStatement(theme.comments.color, length: 25) + } + + private var block2: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 1) { + codeStatement(theme.keywords.color, length: 6) + codeStatement(theme.variables.color, length: 6) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.values.color, length: 8) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.values.color, length: 8) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.keywords.color, length: 6) + codeStatement(theme.strings.color, length: 7) + } + HStack(spacing: 1) { + codeStatement(theme.keywords.color, length: 6) + codeStatement(theme.variables.color, length: 8) + codeStatement(theme.keywords.color, length: 6) + codeStatement(theme.strings.color, length: 12) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeStatement(theme.keywords.color, length: 6) + codeStatement(theme.strings.color, length: 14) + codeStatement(theme.text.color, length: 1) + } + } + } + + private var block3: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 1) { + codeStatement(theme.keywords.color, length: 4) + codeStatement(theme.variables.color, length: 8) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(3) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(5) + codeStatement(theme.text.color, length: 3) + codeStatement(theme.numbers.color, length: 1) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(5) + codeStatement(theme.text.color, length: 6) + codeStatement(theme.strings.color, length: 7) + codeStatement(theme.strings.color, length: 5) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(5) + codeStatement(theme.text.color, length: 5) + codeStatement(theme.keywords.color, length: 5) + } + HStack(spacing: 1) { + codeSpace(3) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeStatement(theme.text.color, length: 2) + } + } + } + + private var block4: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 1) { + codeStatement(theme.keywords.color, length: 6) + codeStatement(theme.keywords.color, length: 7) + codeStatement(theme.commands.color, length: 8) + codeStatement(theme.values.color, length: 3) + codeStatement(theme.text.color, length: 2) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(3) + codeStatement(theme.keywords.color, length: 4) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.variables.color, length: 5) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.values.color, length: 8) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.types.color, length: 8) + codeStatement(theme.text.color, length: 2) + } + } + } + + private var block5: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 1) { + codeSpace(3) + codeStatement(theme.keywords.color, length: 4) + codeStatement(theme.variables.color, length: 10) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.types.color, length: 11) + codeStatement(theme.text.color, length: 3) + codeStatement(theme.keywords.color, length: 2) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(5) + codeStatement(theme.attributes.color, length: 8) + codeStatement(theme.text.color, length: 2) + codeStatement(theme.variables.color, length: 5) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.keywords.color, length: 2) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(7) + codeStatement(theme.keywords.color, length: 3) + codeStatement(theme.variables.color, length: 12) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(9) + codeStatement(theme.text.color, length: 3) + codeStatement(theme.characters.color, length: 1) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(9) + codeStatement(theme.text.color, length: 1) + } + HStack(spacing: 1) { + codeSpace(9) + codeStatement(theme.text.color, length: 3) + codeStatement(theme.attributes.color, length: 5) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.types.color, length: 6) + codeStatement(theme.text.color, length: 1) + codeStatement(theme.numbers.color, length: 1) + codeStatement(theme.text.color, length: 1) + } + } + } + + private func codeStatement(_ color: String, length: Double) -> some View { + Rectangle() + .foregroundColor(Color(hex: color)) + .frame(width: length, height: 2) + } + + private func codeSpace(_ length: Double) -> some View { + Rectangle() + .foregroundColor(.clear) + .frame(width: length-1, height: 2) + } +} + + @available(macOS 12, *) + struct ThemePreviewIcon_Previews: PreviewProvider { + static var previews: some View { + ThemePreviewIcon(ThemeModel.shared.themes.first!, selection: .constant(ThemeModel.shared.themes.first)) + .preferredColorScheme(.light) + + ThemePreviewIcon(ThemeModel.shared.themes.last!, selection: .constant(nil)) + .preferredColorScheme(.dark) + } + } diff --git a/CodeEditModules/Modules/FontPicker/src/FontPickerView.swift b/CodeEditModules/Modules/FontPicker/src/FontPickerView.swift index adf9039ae8..ec3d792c4c 100644 --- a/CodeEditModules/Modules/FontPicker/src/FontPickerView.swift +++ b/CodeEditModules/Modules/FontPicker/src/FontPickerView.swift @@ -66,8 +66,7 @@ public struct FontPicker: View { NSFontPanel.shared.setPanelFont(self.font, isMultiple: false) NSFontPanel.shared.orderBack(nil) } label: { - Image(systemName: "textformat") - .imageScale(.large) + Text("Select...") } .fixedSize() } diff --git a/CodeEditModules/Modules/TerminalEmulator/src/Model/AnsiColors.swift b/CodeEditModules/Modules/TerminalEmulator/src/Model/AnsiColors.swift index e123725b4f..f4e5a19fc4 100644 --- a/CodeEditModules/Modules/TerminalEmulator/src/Model/AnsiColors.swift +++ b/CodeEditModules/Modules/TerminalEmulator/src/Model/AnsiColors.swift @@ -61,6 +61,14 @@ public class AnsiColors: ObservableObject { } public extension Color { + + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + self.init(hex: Int(int)) + } + init(hex: Int) { let red = (hex >> 16) & 0xFF let green = (hex >> 8) & 0xFF @@ -71,10 +79,16 @@ public extension Color { var hex: Int { guard let components = cgColor?.components, components.count >= 3 else { return 0 } - let red = Int(components[0] * 255) << 16 - let green = Int(components[1] * 255) << 8 - let blue = Int(components[2] * 255) + let red = lround((Double(components[0]) * 255.0)) << 16 + let green = lround((Double(components[1]) * 255.0)) << 8 + let blue = lround((Double(components[2]) * 255.0)) return red | green | blue } + + var hexString: String { + let color = self.hex + + return "#" + String(format: "%06x", color) + } } diff --git a/CodeEditModules/Package.swift b/CodeEditModules/Package.swift index 16e82ecf26..fe1d396a33 100644 --- a/CodeEditModules/Package.swift +++ b/CodeEditModules/Package.swift @@ -173,7 +173,8 @@ let package = Package( "FontPicker", "TerminalEmulator", ], - path: "Modules/AppPreferences/src" + path: "Modules/AppPreferences/src", + resources: [.copy("Modules/AppPreferences/resources")] ) ] ) From ca712b793066a0a03286bece8bbba325df68881d Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Thu, 31 Mar 2022 14:25:44 +0200 Subject: [PATCH 05/26] updates --- .../ThemePreferences/PreviewThemeView.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/PreviewThemeView.swift diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/PreviewThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/PreviewThemeView.swift new file mode 100644 index 0000000000..9449f7dfc3 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/PreviewThemeView.swift @@ -0,0 +1,27 @@ +// +// PreviewThemeView.swift +// +// +// Created by Lukas Pistrol on 31.03.22. +// + +import SwiftUI + +struct PreviewThemeView: View { + var body: some View { + ZStack(alignment: .topLeading) { + Rectangle() + .foregroundColor(Color(NSColor.controlBackgroundColor)) + VStack(alignment: .leading, spacing: 0) { + Text("Implementation needed") + } + .padding(20) + } + } +} + +struct PreviewThemeView_Previews: PreviewProvider { + static var previews: some View { + PreviewThemeView() + } +} From e297a34119d19071438e0f20005e738151267cbc Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Thu, 31 Mar 2022 15:31:45 +0200 Subject: [PATCH 06/26] Quickfix - no themes loaded --- .../AppPreferences/src/ThemePreferences/Model/ThemeModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 4383698bd4..33987ba548 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -51,7 +51,7 @@ public class ThemeModel: ObservableObject { var isDir: ObjCBool = false if !filemanager.fileExists(atPath: url.path, isDirectory: &isDir) { - try filemanager.createDirectory(at: url, withIntermediateDirectories: false) + try filemanager.createDirectory(at: url, withIntermediateDirectories: true) } let content = try filemanager.contentsOfDirectory(atPath: url.path).filter { $0.contains(".json") } From 3e8c02db42f6be03d3c3711942c0c84e7e5ce304 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Thu, 31 Mar 2022 16:30:03 +0200 Subject: [PATCH 07/26] adjusted window tint of preview icons --- .../ThemePreferences/ThemePreferencesView.swift | 6 +++++- .../src/ThemePreferences/ThemePreviewIcon.swift | 16 ++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index 494592d5ed..c6404f2acb 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -69,7 +69,11 @@ public struct ThemePreferencesView: View { alignment: .center, spacing: 20) { ForEach(themeModel.selectedAppearance == 0 ? themeModel.darkThemes : themeModel.lightThemes) { theme in - ThemePreviewIcon(theme, selection: $themeModel.selectedTheme) + ThemePreviewIcon( + theme, + selection: $themeModel.selectedTheme, + colorScheme: themeModel.selectedAppearance == 0 ? .dark : .light + ) .transition(.opacity) } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift index 5eaa55c0c9..b91c0a647f 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift @@ -9,12 +9,10 @@ import SwiftUI @available(macOS 12, *) struct ThemePreviewIcon: View { - @Environment(\.colorScheme) - private var colorScheme - - init(_ theme: Theme, selection: Binding) { + init(_ theme: Theme, selection: Binding, colorScheme: ColorScheme) { self.theme = theme self._selection = selection + self.colorScheme = colorScheme } var theme: Theme @@ -22,6 +20,8 @@ struct ThemePreviewIcon: View { @Binding var selection: Theme? + var colorScheme: ColorScheme + var body: some View { VStack { ZStack(alignment: .topLeading) { @@ -263,10 +263,14 @@ struct ThemePreviewIcon: View { @available(macOS 12, *) struct ThemePreviewIcon_Previews: PreviewProvider { static var previews: some View { - ThemePreviewIcon(ThemeModel.shared.themes.first!, selection: .constant(ThemeModel.shared.themes.first)) + ThemePreviewIcon(ThemeModel.shared.themes.first!, + selection: .constant(ThemeModel.shared.themes.first), + colorScheme: .light) .preferredColorScheme(.light) - ThemePreviewIcon(ThemeModel.shared.themes.last!, selection: .constant(nil)) + ThemePreviewIcon(ThemeModel.shared.themes.last!, + selection: .constant(nil), + colorScheme: .dark) .preferredColorScheme(.dark) } } From b9cd31a931fe4432b928036a84be2d487ce397e7 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Thu, 31 Mar 2022 17:26:28 +0200 Subject: [PATCH 08/26] added terminal color support for themes. --- CodeEdit.xcodeproj/project.pbxproj | 2 +- CodeEdit/DefaultThemes/default-dark.json | 65 +++++++++++++ .../ThemePreferences/Model/Color+HEX.swift | 41 ++++++++ .../Model/TerminalColorScheme.swift | 0 .../Model/TerminalFont.swift | 0 .../Model/TerminalShellType.swift | 0 .../src/ThemePreferences/Model/Theme.swift | 97 ++++++++++++++++++- .../ThemePreferences/Model/ThemeModel.swift | 5 +- .../ThemePreferences/TerminalThemeView.swift | 61 ++++++------ .../ThemePreferencesView.swift | 1 - .../src/Extensions/SwiftTerm+Color+Init.swift | 9 ++ .../src/Model/AnsiColors.swift | 94 ------------------ .../src/TerminalEmulatorView.swift | 11 ++- CodeEditModules/Package.swift | 6 +- 14 files changed, 259 insertions(+), 133 deletions(-) create mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Color+HEX.swift rename CodeEditModules/Modules/{TerminalEmulator/src => AppPreferences/src/ThemePreferences}/Model/TerminalColorScheme.swift (100%) rename CodeEditModules/Modules/{TerminalEmulator/src => AppPreferences/src/ThemePreferences}/Model/TerminalFont.swift (100%) rename CodeEditModules/Modules/{TerminalEmulator/src => AppPreferences/src/ThemePreferences}/Model/TerminalShellType.swift (100%) delete mode 100644 CodeEditModules/Modules/TerminalEmulator/src/Model/AnsiColors.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index d8825520a4..737e36381c 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -86,7 +86,7 @@ 0485EB1C27E7338100138301 /* QuickOpenItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickOpenItem.swift; sourceTree = ""; }; 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceCodeFileView.swift; sourceTree = ""; }; 0485EB2227E7791400138301 /* QuickOpenPreviewView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = QuickOpenPreviewView.swift; sourceTree = ""; tabWidth = 4; }; - 28069DA327F5BCDD0016BC47 /* default-dark.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "default-dark.json"; sourceTree = ""; }; + 28069DA327F5BCDD0016BC47 /* default-dark.json */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = text.json; path = "default-dark.json"; sourceTree = ""; tabWidth = 2; }; 2806CBD327EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorContextMenu.swift; sourceTree = ""; }; 286471AA27ED51FD0039369D /* ProjectNavigator.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = ProjectNavigator.swift; sourceTree = ""; tabWidth = 4; }; 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = BreadcrumbsComponent.swift; sourceTree = ""; tabWidth = 4; }; diff --git a/CodeEdit/DefaultThemes/default-dark.json b/CodeEdit/DefaultThemes/default-dark.json index 4261c74bd5..de44c684b0 100644 --- a/CodeEdit/DefaultThemes/default-dark.json +++ b/CodeEdit/DefaultThemes/default-dark.json @@ -54,5 +54,70 @@ }, "invisibles" : { "color" : "#636363" + }, + "terminal" : { + "text" : { + "color" : "#ffffff" + }, + "boldText" : { + "color" : "#ffffff" + }, + "cursor" : { + "color" : "#ffffff" + }, + "background" : { + "color" : "#1e1e1e" + }, + "selection" : { + "color" : "#8b8b8b" + }, + "black" : { + "color" : "#000000" + }, + "red" : { + "color" : "#990000" + }, + "green" : { + "color" : "#00a600" + }, + "yellow" : { + "color" : "#999900" + }, + "blue" : { + "color" : "#0000b2" + }, + "magenta" : { + "color" : "#b200b2" + }, + "cyan" : { + "color" : "#00a6b2" + }, + "white" : { + "color" : "#bfbfbf" + }, + "brightBlack" : { + "color" : "#666666" + }, + "brightRed" : { + "color" : "#e50000" + }, + "brightGreen" : { + "color" : "#00d900" + }, + "brightYellow" : { + "color" : "#e5e500" + }, + "brightBlue" : { + "color" : "#0000ff" + }, + "brightMagenta" : { + "color" : "#e500e5" + }, + "brightCyan" : { + "color" : "#00e5e5" + }, + "brightWhite" : { + "color" : "#e5e5e5" + } } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Color+HEX.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Color+HEX.swift new file mode 100644 index 0000000000..bef04a820a --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Color+HEX.swift @@ -0,0 +1,41 @@ +// +// File.swift +// +// +// Created by Lukas Pistrol on 23.03.22. +// + +import SwiftUI + +public extension Color { + + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + self.init(hex: Int(int)) + } + + init(hex: Int) { + let red = (hex >> 16) & 0xFF + let green = (hex >> 8) & 0xFF + let blue = hex & 0xFF + self.init(.sRGB, red: Double(red) / 255, green: Double(green) / 255, blue: Double(blue) / 255, opacity: 1) + } + + var hex: Int { + guard let components = cgColor?.components, components.count >= 3 else { return 0 } + + let red = lround((Double(components[0]) * 255.0)) << 16 + let green = lround((Double(components[1]) * 255.0)) << 8 + let blue = lround((Double(components[2]) * 255.0)) + + return red | green | blue + } + + var hexString: String { + let color = self.hex + + return "#" + String(format: "%06x", color) + } +} diff --git a/CodeEditModules/Modules/TerminalEmulator/src/Model/TerminalColorScheme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalColorScheme.swift similarity index 100% rename from CodeEditModules/Modules/TerminalEmulator/src/Model/TerminalColorScheme.swift rename to CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalColorScheme.swift diff --git a/CodeEditModules/Modules/TerminalEmulator/src/Model/TerminalFont.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalFont.swift similarity index 100% rename from CodeEditModules/Modules/TerminalEmulator/src/Model/TerminalFont.swift rename to CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalFont.swift diff --git a/CodeEditModules/Modules/TerminalEmulator/src/Model/TerminalShellType.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalShellType.swift similarity index 100% rename from CodeEditModules/Modules/TerminalEmulator/src/Model/TerminalShellType.swift rename to CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalShellType.swift diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift index 90699bfacd..09235e664c 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -33,6 +33,7 @@ public struct Theme: Identifiable, Codable, Equatable { public var characters: Attributes public var comments: Attributes public var metadata: Metadata + public var terminal: TerminalColors public init( text: Attributes, @@ -51,7 +52,8 @@ public struct Theme: Identifiable, Codable, Equatable { strings: Attributes, characters: Attributes, comments: Attributes, - metadata: Metadata + metadata: Metadata, + terminal: TerminalColors ) { self.text = text self.insertionPoint = insertionPoint @@ -70,6 +72,7 @@ public struct Theme: Identifiable, Codable, Equatable { self.characters = characters self.comments = comments self.metadata = metadata + self.terminal = terminal } } @@ -91,6 +94,98 @@ public struct Attributes: Codable { } } +public struct TerminalColors: Codable { + + public var text: Attributes + public var boldText: Attributes + public var cursor: Attributes + public var background: Attributes + public var selection: Attributes + public var black: Attributes + public var red: Attributes + public var green: Attributes + public var yellow: Attributes + public var blue: Attributes + public var magenta: Attributes + public var cyan: Attributes + public var white: Attributes + public var brightBlack: Attributes + public var brightRed: Attributes + public var brightGreen: Attributes + public var brightYellow: Attributes + public var brightBlue: Attributes + public var brightMagenta: Attributes + public var brightCyan: Attributes + public var brightWhite: Attributes + + public var ansiColors: [String] { + [ + black.color, + red.color, + green.color, + yellow.color, + blue.color, + magenta.color, + cyan.color, + white.color, + brightBlack.color, + brightRed.color, + brightGreen.color, + brightYellow.color, + brightBlue.color, + brightMagenta.color, + brightCyan.color, + brightWhite.color, + ] + } + + init( + text: Attributes, + boldText: Attributes, + cursor: Attributes, + background: Attributes, + selection: Attributes, + black: Attributes, + red: Attributes, + green: Attributes, + yellow: Attributes, + blue: Attributes, + magenta: Attributes, + cyan: Attributes, + white: Attributes, + brightBlack: Attributes, + brightRed: Attributes, + brightGreen: Attributes, + brightYellow: Attributes, + brightBlue: Attributes, + brightMagenta: Attributes, + brightCyan: Attributes, + brightWhite: Attributes + ) { + self.text = text + self.boldText = boldText + self.cursor = cursor + self.background = background + self.selection = selection + self.black = black + self.red = red + self.green = green + self.yellow = yellow + self.blue = blue + self.magenta = magenta + self.cyan = cyan + self.white = white + self.brightBlack = brightBlack + self.brightRed = brightRed + self.brightGreen = brightGreen + self.brightYellow = brightYellow + self.brightBlue = brightBlue + self.brightMagenta = brightMagenta + self.brightCyan = brightCyan + self.brightWhite = brightWhite + } +} + // MARK: - Metadata public struct Metadata: Codable { public var author, license, metadataDescription: String diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 33987ba548..34aa3f3583 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -19,7 +19,10 @@ public class ThemeModel: ObservableObject { @Published public var themes: [Theme] = [] { - didSet { try? saveThemes() } + didSet { + try? saveThemes() + objectWillChange.send() + } } @Published diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift index faacd1d11d..c1a1d7eee0 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import TerminalEmulator import FontPicker struct TerminalThemeView: View { @@ -26,7 +25,7 @@ struct TerminalThemeView: View { var terminalColorSchmeme: TerminalColorScheme = .default @StateObject - private var colors = AnsiColors.shared + private var themeModel: ThemeModel = .shared @State private var darkTerminal: Bool = false @@ -107,36 +106,36 @@ struct TerminalThemeView: View { private var ansiColorSelector: some View { VStack(alignment: .leading, spacing: 5) { - Text("ANSI Colors") - .font(.system(size: 13, weight: .semibold)) - .foregroundColor(.secondary) - .padding(.bottom, 5) - HStack(spacing: 5) { - PreferencesColorPicker($colors.colors[0]) - PreferencesColorPicker($colors.colors[1]) - PreferencesColorPicker($colors.colors[2]) - PreferencesColorPicker($colors.colors[3]) - PreferencesColorPicker($colors.colors[4]) - PreferencesColorPicker($colors.colors[5]) - PreferencesColorPicker($colors.colors[6]) - PreferencesColorPicker($colors.colors[7]) - Text("Normal").padding(.leading, 4) - } - HStack(spacing: 5) { - PreferencesColorPicker($colors.colors[8]) - PreferencesColorPicker($colors.colors[9]) - PreferencesColorPicker($colors.colors[10]) - PreferencesColorPicker($colors.colors[11]) - PreferencesColorPicker($colors.colors[12]) - PreferencesColorPicker($colors.colors[13]) - PreferencesColorPicker($colors.colors[14]) - PreferencesColorPicker($colors.colors[15]) - Text("Bright").padding(.leading, 4) - } - Button("Restore Defaults") { - AnsiColors.shared.resetDefault() + if let selectedTheme = themeModel.selectedTheme, + let index = themeModel.themes.firstIndex(of: selectedTheme) { + Text("ANSI Colors") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.secondary) + .padding(.bottom, 5) + HStack(spacing: 5) { + PreferencesColorPicker($themeModel.themes[index].terminal.black.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.red.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.green.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.yellow.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.blue.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.magenta.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.cyan.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.white.swiftColor) + Text("Normal").padding(.leading, 4) + } + HStack(spacing: 5) { + PreferencesColorPicker($themeModel.themes[index].terminal.brightBlack.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.brightRed.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.brightGreen.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.brightYellow.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.brightBlue.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.brightMagenta.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.brightCyan.swiftColor) + PreferencesColorPicker($themeModel.themes[index].terminal.brightWhite.swiftColor) + Text("Bright").padding(.leading, 4) + } + .padding(.top, 5) } - .padding(.top, 5) } } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index c6404f2acb..fbe06125f0 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -9,7 +9,6 @@ import CodeFile import FontPicker import SwiftUI import Preferences -import TerminalEmulator // swiftlint:disable all @available(macOS 12, *) diff --git a/CodeEditModules/Modules/TerminalEmulator/src/Extensions/SwiftTerm+Color+Init.swift b/CodeEditModules/Modules/TerminalEmulator/src/Extensions/SwiftTerm+Color+Init.swift index 9eb40ffdb8..625ea22ea5 100644 --- a/CodeEditModules/Modules/TerminalEmulator/src/Extensions/SwiftTerm+Color+Init.swift +++ b/CodeEditModules/Modules/TerminalEmulator/src/Extensions/SwiftTerm+Color+Init.swift @@ -5,6 +5,7 @@ // Created by Lukas Pistrol on 24.03.22. // +import Foundation import SwiftTerm internal extension SwiftTerm.Color { @@ -31,4 +32,12 @@ internal extension SwiftTerm.Color { let blue = UInt8(hex & 0xFF) self.init(iRed: red, green: green, blue: blue) } + + /// 0x000000 - 0xFFFFFF + convenience init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + self.init(hex: Int(int)) + } } diff --git a/CodeEditModules/Modules/TerminalEmulator/src/Model/AnsiColors.swift b/CodeEditModules/Modules/TerminalEmulator/src/Model/AnsiColors.swift deleted file mode 100644 index f4e5a19fc4..0000000000 --- a/CodeEditModules/Modules/TerminalEmulator/src/Model/AnsiColors.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// File.swift -// -// -// Created by Lukas Pistrol on 23.03.22. -// - -import SwiftUI - -/// Wrapper for storing `Color` objects as 24-bit integer in UserDefaults -public class AnsiColors: ObservableObject { - // public static let `default` = - public static let storageKey: String = "ANSIColors" - public static let shared: AnsiColors = .init() - - public init() { - guard let loadColors = UserDefaults.standard.object(forKey: Self.storageKey) as? [Int] else { - resetDefault() - return - } - self.mappedColors = loadColors - } - - public func resetDefault() { - colors.removeAll() - colors.append(Color(red: 0.000, green: 0.000, blue: 0.000)) - colors.append(Color(red: 0.600, green: 0.000, blue: 0.000)) - colors.append(Color(red: 0.000, green: 0.651, blue: 0.004)) - colors.append(Color(red: 0.600, green: 0.600, blue: 0.000)) - colors.append(Color(red: 0.000, green: 0.031, blue: 0.702)) - colors.append(Color(red: 0.702, green: 0.020, blue: 0.702)) - colors.append(Color(red: 0.000, green: 0.647, blue: 0.702)) - colors.append(Color(red: 0.749, green: 0.749, blue: 0.749)) - colors.append(Color(red: 0.400, green: 0.400, blue: 0.400)) - colors.append(Color(red: 0.902, green: 0.000, blue: 0.004)) - colors.append(Color(red: 0.004, green: 0.851, blue: 0.000)) - colors.append(Color(red: 0.902, green: 0.898, blue: 0.012)) - colors.append(Color(red: 0.000, green: 0.063, blue: 1.000)) - colors.append(Color(red: 0.902, green: 0.035, blue: 0.902)) - colors.append(Color(red: 0.008, green: 0.902, blue: 0.898)) - colors.append(Color(red: 0.902, green: 0.902, blue: 0.902)) - } - - @Published - public var mappedColors: [Int] = [] { - didSet { - UserDefaults.standard.set(mappedColors, forKey: AnsiColors.storageKey) - } - } - - public var colors: [Color] { - get { - let mapped = self.mappedColors.map { Color(hex: $0) } - return mapped - } - set { - self.mappedColors = newValue.map { $0.hex } - } - - } -} - -public extension Color { - - init(hex: String) { - let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) - var int: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&int) - self.init(hex: Int(int)) - } - - init(hex: Int) { - let red = (hex >> 16) & 0xFF - let green = (hex >> 8) & 0xFF - let blue = hex & 0xFF - self.init(.sRGB, red: Double(red) / 255, green: Double(green) / 255, blue: Double(blue) / 255, opacity: 1) - } - - var hex: Int { - guard let components = cgColor?.components, components.count >= 3 else { return 0 } - - let red = lround((Double(components[0]) * 255.0)) << 16 - let green = lround((Double(components[1]) * 255.0)) << 8 - let blue = lround((Double(components[2]) * 255.0)) - - return red | green | blue - } - - var hexString: String { - let color = self.hex - - return "#" + String(format: "%06x", color) - } -} diff --git a/CodeEditModules/Modules/TerminalEmulator/src/TerminalEmulatorView.swift b/CodeEditModules/Modules/TerminalEmulator/src/TerminalEmulatorView.swift index 49566bc5ae..c96e84ef85 100644 --- a/CodeEditModules/Modules/TerminalEmulator/src/TerminalEmulatorView.swift +++ b/CodeEditModules/Modules/TerminalEmulator/src/TerminalEmulatorView.swift @@ -7,6 +7,7 @@ import SwiftUI import SwiftTerm +import AppPreferences /// # TerminalEmulatorView /// @@ -32,7 +33,7 @@ public struct TerminalEmulatorView: NSViewRepresentable { var terminalColorSchmeme: TerminalColorScheme = .default @StateObject - private var ansiColors: AnsiColors = .shared + private var themeModel: ThemeModel = .shared internal static var lastTerminal: LocalProcessTerminalView? @@ -99,7 +100,13 @@ public struct TerminalEmulatorView: NSViewRepresentable { /// Returns the mapped array of `SwiftTerm.Color` objects of ANSI Colors private var colors: [SwiftTerm.Color] { - return ansiColors.mappedColors.map { SwiftTerm.Color(hex: $0) } + if let selectedTheme = themeModel.selectedTheme, + let index = themeModel.themes.firstIndex(of: selectedTheme) { + return themeModel.themes[index].terminal.ansiColors.map { color in + SwiftTerm.Color(hex: color) + } + } + return [] } /// returns a `NSAppearance` based on the user setting of the terminal appearance, diff --git a/CodeEditModules/Package.swift b/CodeEditModules/Package.swift index fe1d396a33..b57e08593f 100644 --- a/CodeEditModules/Package.swift +++ b/CodeEditModules/Package.swift @@ -147,7 +147,10 @@ let package = Package( ), .target( name: "TerminalEmulator", - dependencies: ["SwiftTerm"], + dependencies: [ + "SwiftTerm", + "AppPreferences" + ], path: "Modules/TerminalEmulator/src" ), .target( @@ -171,7 +174,6 @@ let package = Package( "Preferences", "CodeFile", "FontPicker", - "TerminalEmulator", ], path: "Modules/AppPreferences/src", resources: [.copy("Modules/AppPreferences/resources")] From e48c035b4d2691bd6f1231b99f89d7db1ba17e5d Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Thu, 31 Mar 2022 18:32:02 +0200 Subject: [PATCH 09/26] updated theme model --- CodeEdit/DefaultThemes/default-dark.json | 111 ++++++------ .../ThemePreferences/EditorThemeView.swift | 32 ++-- .../src/ThemePreferences/Model/Theme.swift | 118 ++++++------- .../ThemePreferences/Model/ThemeModel.swift | 14 +- .../ThemePreferences/ThemePreviewIcon.swift | 161 +++++++++--------- 5 files changed, 222 insertions(+), 214 deletions(-) diff --git a/CodeEdit/DefaultThemes/default-dark.json b/CodeEdit/DefaultThemes/default-dark.json index de44c684b0..7eaf710757 100644 --- a/CodeEdit/DefaultThemes/default-dark.json +++ b/CodeEdit/DefaultThemes/default-dark.json @@ -1,59 +1,60 @@ { - "strings" : { - "color" : "#f0907f" - }, - "comments" : { - "color" : "#97be71" - }, - "numbers" : { - "color" : "#d6c775" - }, - "metadata" : { - "darkTheme" : true, - "license" : "MIT", - "author" : "CodeEdit", - "description" : "CodeEdit default dark theme.", - "name" : "Default (Dark)", - "distributionURL" : "https:\/\/github.com\/CodeEditApp\/CodeEdit" - }, - "commands" : { - "color" : "#c6a3f9" - }, - "lineHighlight" : { - "color" : "#303030" - }, - "values" : { - "color" : "#70c1e2" - }, - "background" : { - "color" : "#1e1e1e" - }, - "keywords" : { - "color" : "#ef8bb6" - }, - "text" : { - "color" : "#DDDDDD" - }, - "insertionPoint" : { - "color" : "#DDDDDD" - }, - "selection" : { - "color" : "#8b8b8b" - }, - "types" : { - "color" : "#93c7bc" - }, - "variables" : { - "color" : "#70c1e2" - }, - "attributes" : { - "color" : "#93c7bc" - }, - "characters" : { - "color" : "#93c7bc" - }, - "invisibles" : { - "color" : "#636363" + "darkTheme" : true, + "license" : "MIT", + "author" : "CodeEdit", + "description" : "CodeEdit default dark theme.", + "name" : "Default (Dark)", + "distributionURL" : "https:\/\/github.com\/CodeEditApp\/CodeEdit", + "version" : "0.0.1", + "editor" : { + "strings" : { + "color" : "#f0907f" + }, + "comments" : { + "color" : "#97be71" + }, + "numbers" : { + "color" : "#d6c775" + }, + "commands" : { + "color" : "#c6a3f9" + }, + "lineHighlight" : { + "color" : "#303030" + }, + "values" : { + "color" : "#70c1e2" + }, + "background" : { + "color" : "#1e1e1e" + }, + "keywords" : { + "color" : "#ef8bb6" + }, + "text" : { + "color" : "#DDDDDD" + }, + "insertionPoint" : { + "color" : "#DDDDDD" + }, + "selection" : { + "color" : "#8b8b8b" + }, + "types" : { + "color" : "#93c7bc" + }, + "variables" : { + "color" : "#70c1e2" + }, + "attributes" : { + "color" : "#93c7bc" + }, + "characters" : { + "color" : "#93c7bc" + }, + "invisibles" : { + "color" : "#636363" + } }, "terminal" : { "text" : { diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift index 8498c46bff..175980814f 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift @@ -23,20 +23,20 @@ struct EditorThemeView: View { .padding(.bottom, 20) HStack(spacing: 0) { VStack(alignment: .leading, spacing: 10) { - PreferencesColorPicker($themeModel.themes[index].text.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.text.swiftColor, label: "Text") - PreferencesColorPicker($themeModel.themes[index].insertionPoint.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.insertionPoint.swiftColor, label: "Cursor") - PreferencesColorPicker($themeModel.themes[index].invisibles.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.invisibles.swiftColor, label: "Invisibles") } .frame(maxWidth: .infinity, alignment: .leading) VStack(alignment: .leading, spacing: 10) { - PreferencesColorPicker($themeModel.themes[index].background.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.background.swiftColor, label: "Background") - PreferencesColorPicker($themeModel.themes[index].lineHighlight.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.lineHighlight.swiftColor, label: "Current Line") - PreferencesColorPicker($themeModel.themes[index].selection.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.selection.swiftColor, label: "Selection") } .frame(maxWidth: .infinity, alignment: .leading) @@ -48,28 +48,28 @@ struct EditorThemeView: View { .padding(.bottom, 10) HStack(spacing: 0) { VStack(alignment: .leading, spacing: 10) { - PreferencesColorPicker($themeModel.themes[index].keywords.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.keywords.swiftColor, label: "Keywords") - PreferencesColorPicker($themeModel.themes[index].commands.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.commands.swiftColor, label: "Commands") - PreferencesColorPicker($themeModel.themes[index].types.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.types.swiftColor, label: "Types") - PreferencesColorPicker($themeModel.themes[index].attributes.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.attributes.swiftColor, label: "Attributes") - PreferencesColorPicker($themeModel.themes[index].variables.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.variables.swiftColor, label: "Variables") } .frame(maxWidth: .infinity, alignment: .leading) VStack(alignment: .leading, spacing: 10) { - PreferencesColorPicker($themeModel.themes[index].values.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.values.swiftColor, label: "Values") - PreferencesColorPicker($themeModel.themes[index].numbers.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.numbers.swiftColor, label: "Numbers") - PreferencesColorPicker($themeModel.themes[index].strings.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.strings.swiftColor, label: "Strings") - PreferencesColorPicker($themeModel.themes[index].characters.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.characters.swiftColor, label: "Characters") - PreferencesColorPicker($themeModel.themes[index].comments.swiftColor, + PreferencesColorPicker($themeModel.themes[index].editor.comments.swiftColor, label: "Comments") } .frame(maxWidth: .infinity, alignment: .leading) diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift index 09235e664c..e3e01f6e41 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -10,12 +10,70 @@ import SwiftUI // MARK: - Theme public struct Theme: Identifiable, Codable, Equatable { + enum CodingKeys: String, CodingKey { + case author, license, distributionURL, name, darkTheme, editor, terminal, version + case metadataDescription = "description" + } + public static func == (lhs: Theme, rhs: Theme) -> Bool { lhs.id == rhs.id } - public var id: String { metadata.name } + public var id: String { self.name } + + public var author: String + public var license: String + public var metadataDescription: String + public var distributionURL: String + public var name: String + public var version: String + public var darkTheme: Bool + + public var editor: EditorColors + public var terminal: TerminalColors + + public init( + editor: EditorColors, + terminal: TerminalColors, + author: String, + license: String, + metadataDescription: String, + distributionURL: String, + name: String, + darkTheme: Bool, + version: String + ) { + self.author = author + self.license = license + self.metadataDescription = metadataDescription + self.distributionURL = distributionURL + self.name = name + self.darkTheme = darkTheme + self.version = version + self.editor = editor + self.terminal = terminal + } +} + +// MARK: - Attributes +public struct Attributes: Codable { + public var color: String + + public init(color: String) { + self.color = color + } + + public internal(set) var swiftColor: Color { + get { + Color(hex: color) + } + set { + self.color = newValue.hexString + } + } +} +public struct EditorColors: Codable { public var text: Attributes public var insertionPoint: Attributes public var invisibles: Attributes @@ -32,8 +90,6 @@ public struct Theme: Identifiable, Codable, Equatable { public var strings: Attributes public var characters: Attributes public var comments: Attributes - public var metadata: Metadata - public var terminal: TerminalColors public init( text: Attributes, @@ -51,9 +107,7 @@ public struct Theme: Identifiable, Codable, Equatable { numbers: Attributes, strings: Attributes, characters: Attributes, - comments: Attributes, - metadata: Metadata, - terminal: TerminalColors + comments: Attributes ) { self.text = text self.insertionPoint = insertionPoint @@ -71,26 +125,6 @@ public struct Theme: Identifiable, Codable, Equatable { self.strings = strings self.characters = characters self.comments = comments - self.metadata = metadata - self.terminal = terminal - } -} - -// MARK: - Attributes -public struct Attributes: Codable { - public var color: String - - public init(color: String) { - self.color = color - } - - public internal(set) var swiftColor: Color { - get { - Color(hex: color) - } - set { - self.color = newValue.hexString - } } } @@ -185,35 +219,3 @@ public struct TerminalColors: Codable { self.brightWhite = brightWhite } } - -// MARK: - Metadata -public struct Metadata: Codable { - public var author, license, metadataDescription: String - public var distributionUrl: String - public var name: String - public var darkTheme: Bool - - enum CodingKeys: String, CodingKey { - case author, license - case metadataDescription = "description" - case distributionUrl = "distributionURL" - case name - case darkTheme - } - - public init( - author: String, - license: String, - metadataDescription: String, - distributionUrl: String, - name: String, - darkTheme: Bool - ) { - self.author = author - self.license = license - self.metadataDescription = metadataDescription - self.distributionUrl = distributionUrl - self.name = name - self.darkTheme = darkTheme - } -} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 34aa3f3583..2c6a7b2535 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -29,16 +29,20 @@ public class ThemeModel: ObservableObject { public var selectedTheme: Theme? public var darkThemes: [Theme] { - themes.filter { $0.metadata.darkTheme } + themes.filter { $0.darkTheme } } public var lightThemes: [Theme] { - themes.filter { !$0.metadata.darkTheme } + themes.filter { !$0.darkTheme } } init() { - try? loadThemes() - self.selectedTheme = themes.first + do { + try loadThemes() + self.selectedTheme = themes.first + } catch { + print(error) + } } private func load(from url: URL) throws { @@ -78,7 +82,7 @@ public class ThemeModel: ObservableObject { let json = try JSONSerialization.jsonObject(with: data) let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) try prettyJSON.write( - to: url.appendingPathComponent(theme.metadata.name).appendingPathExtension("json"), + to: url.appendingPathComponent(theme.name).appendingPathExtension("json"), options: .atomic ) } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift index b91c0a647f..907f6f06be 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreviewIcon.swift @@ -43,13 +43,14 @@ struct ThemePreviewIcon: View { .strokeBorder(lineWidth: 2) .foregroundColor(selection == theme ? .accentColor : .clear) } - Text(theme.metadata.name) + Text(theme.name) .font(.subheadline) .padding(.horizontal, 7) .padding(.vertical, 2) .foregroundColor(selection == theme ? .white : .primary) .background(Capsule().foregroundColor(selection == theme ? .accentColor : .clear)) } + .help(theme.metadataDescription) .onTapGesture { withAnimation(.interactiveSpring()) { self.selection = theme @@ -79,7 +80,7 @@ struct ThemePreviewIcon: View { .foregroundColor(Color(hex: colorScheme == .dark ? 0x2b2b2b : 0xe0e0e0)) .frame(height: 10) Rectangle() - .foregroundColor(theme.background.swiftColor) + .foregroundColor(theme.editor.background.swiftColor) .overlay(alignment: .topLeading) { codeWindow } @@ -99,34 +100,34 @@ struct ThemePreviewIcon: View { } private var block1: some View { - codeStatement(theme.comments.color, length: 25) + codeStatement(theme.editor.comments.color, length: 25) } private var block2: some View { VStack(alignment: .leading, spacing: 1) { HStack(spacing: 1) { - codeStatement(theme.keywords.color, length: 6) - codeStatement(theme.variables.color, length: 6) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.values.color, length: 8) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.values.color, length: 8) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.keywords.color, length: 6) - codeStatement(theme.strings.color, length: 7) + codeStatement(theme.editor.keywords.color, length: 6) + codeStatement(theme.editor.variables.color, length: 6) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.values.color, length: 8) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.values.color, length: 8) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.keywords.color, length: 6) + codeStatement(theme.editor.strings.color, length: 7) } HStack(spacing: 1) { - codeStatement(theme.keywords.color, length: 6) - codeStatement(theme.variables.color, length: 8) - codeStatement(theme.keywords.color, length: 6) - codeStatement(theme.strings.color, length: 12) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.keywords.color, length: 6) + codeStatement(theme.editor.variables.color, length: 8) + codeStatement(theme.editor.keywords.color, length: 6) + codeStatement(theme.editor.strings.color, length: 12) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { - codeStatement(theme.keywords.color, length: 6) - codeStatement(theme.strings.color, length: 14) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.keywords.color, length: 6) + codeStatement(theme.editor.strings.color, length: 14) + codeStatement(theme.editor.text.color, length: 1) } } } @@ -134,39 +135,39 @@ struct ThemePreviewIcon: View { private var block3: some View { VStack(alignment: .leading, spacing: 1) { HStack(spacing: 1) { - codeStatement(theme.keywords.color, length: 4) - codeStatement(theme.variables.color, length: 8) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.keywords.color, length: 4) + codeStatement(theme.editor.variables.color, length: 8) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(3) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(5) - codeStatement(theme.text.color, length: 3) - codeStatement(theme.numbers.color, length: 1) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 3) + codeStatement(theme.editor.numbers.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(5) - codeStatement(theme.text.color, length: 6) - codeStatement(theme.strings.color, length: 7) - codeStatement(theme.strings.color, length: 5) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 6) + codeStatement(theme.editor.strings.color, length: 7) + codeStatement(theme.editor.strings.color, length: 5) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(5) - codeStatement(theme.text.color, length: 5) - codeStatement(theme.keywords.color, length: 5) + codeStatement(theme.editor.text.color, length: 5) + codeStatement(theme.editor.keywords.color, length: 5) } HStack(spacing: 1) { codeSpace(3) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { - codeStatement(theme.text.color, length: 2) + codeStatement(theme.editor.text.color, length: 2) } } } @@ -174,24 +175,24 @@ struct ThemePreviewIcon: View { private var block4: some View { VStack(alignment: .leading, spacing: 1) { HStack(spacing: 1) { - codeStatement(theme.keywords.color, length: 6) - codeStatement(theme.keywords.color, length: 7) - codeStatement(theme.commands.color, length: 8) - codeStatement(theme.values.color, length: 3) - codeStatement(theme.text.color, length: 2) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.keywords.color, length: 6) + codeStatement(theme.editor.keywords.color, length: 7) + codeStatement(theme.editor.commands.color, length: 8) + codeStatement(theme.editor.values.color, length: 3) + codeStatement(theme.editor.text.color, length: 2) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(3) - codeStatement(theme.keywords.color, length: 4) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.variables.color, length: 5) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.values.color, length: 8) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.types.color, length: 8) - codeStatement(theme.text.color, length: 2) + codeStatement(theme.editor.keywords.color, length: 4) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.variables.color, length: 5) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.values.color, length: 8) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.types.color, length: 8) + codeStatement(theme.editor.text.color, length: 2) } } } @@ -200,49 +201,49 @@ struct ThemePreviewIcon: View { VStack(alignment: .leading, spacing: 1) { HStack(spacing: 1) { codeSpace(3) - codeStatement(theme.keywords.color, length: 4) - codeStatement(theme.variables.color, length: 10) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.types.color, length: 11) - codeStatement(theme.text.color, length: 3) - codeStatement(theme.keywords.color, length: 2) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.keywords.color, length: 4) + codeStatement(theme.editor.variables.color, length: 10) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.types.color, length: 11) + codeStatement(theme.editor.text.color, length: 3) + codeStatement(theme.editor.keywords.color, length: 2) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(5) - codeStatement(theme.attributes.color, length: 8) - codeStatement(theme.text.color, length: 2) - codeStatement(theme.variables.color, length: 5) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.keywords.color, length: 2) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.attributes.color, length: 8) + codeStatement(theme.editor.text.color, length: 2) + codeStatement(theme.editor.variables.color, length: 5) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.keywords.color, length: 2) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(7) - codeStatement(theme.keywords.color, length: 3) - codeStatement(theme.variables.color, length: 12) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.keywords.color, length: 3) + codeStatement(theme.editor.variables.color, length: 12) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(9) - codeStatement(theme.text.color, length: 3) - codeStatement(theme.characters.color, length: 1) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 3) + codeStatement(theme.editor.characters.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(9) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) } HStack(spacing: 1) { codeSpace(9) - codeStatement(theme.text.color, length: 3) - codeStatement(theme.attributes.color, length: 5) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.types.color, length: 6) - codeStatement(theme.text.color, length: 1) - codeStatement(theme.numbers.color, length: 1) - codeStatement(theme.text.color, length: 1) + codeStatement(theme.editor.text.color, length: 3) + codeStatement(theme.editor.attributes.color, length: 5) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.types.color, length: 6) + codeStatement(theme.editor.text.color, length: 1) + codeStatement(theme.editor.numbers.color, length: 1) + codeStatement(theme.editor.text.color, length: 1) } } } From c5fe3e6719b2bfb3ccf0e2d7dfa6955f4e8b3494 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Fri, 1 Apr 2022 10:08:08 +0200 Subject: [PATCH 10/26] implemented `~/.codeedit/preferences.json --- .../xcshareddata/swiftpm/Package.resolved | 2 +- CodeEdit/AppDelegate.swift | 12 +- .../Breadcrumbs/BreadcrumbsComponent.swift | 7 +- CodeEdit/DefaultThemes/default-dark.json | 2 +- CodeEdit/Info.plist | 2 +- .../ProjectNavigatorItem.swift | 7 +- CodeEdit/TabBar/TabBarItem.swift | 6 +- CodeEdit/Welcome/WelcomeView.swift | 8 +- .../PreferencesGeneralView.swift | 56 +++ .../src/Model/AppPreferences.swift | 160 +++++++++ .../src/Model/AppPreferencesModel.swift | 55 +++ .../src/Model/Appearances.swift | 30 -- .../src/Model/FileIconStyle.swift | 16 - .../src/Model/ReopenBehavior.swift | 17 - .../src/PreferencesGeneralView.swift | 62 ---- .../PreferencesTextEditingView.swift | 11 +- .../src/ThemePreferences/Model/Theme.swift | 324 +++++++++--------- .../ThemePreferences/Model/ThemeModel.swift | 4 +- .../ThemePreferencesView.swift | 1 - .../CodeFile/src/Model/EditorTabWidth.swift | 6 - .../src/TextView/CodeEditorTextView.swift | 8 +- .../StatusBarIndentSelector.swift | 9 +- CodeEditModules/Package.swift | 5 +- 23 files changed, 485 insertions(+), 325 deletions(-) create mode 100644 CodeEditModules/Modules/AppPreferences/src/GeneralPreferences/PreferencesGeneralView.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift create mode 100644 CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift delete mode 100644 CodeEditModules/Modules/AppPreferences/src/Model/Appearances.swift delete mode 100644 CodeEditModules/Modules/AppPreferences/src/Model/FileIconStyle.swift delete mode 100644 CodeEditModules/Modules/AppPreferences/src/Model/ReopenBehavior.swift delete mode 100644 CodeEditModules/Modules/AppPreferences/src/PreferencesGeneralView.swift rename CodeEditModules/Modules/AppPreferences/src/{ => TextEditingPreferences}/PreferencesTextEditingView.swift (50%) delete mode 100644 CodeEditModules/Modules/CodeFile/src/Model/EditorTabWidth.swift diff --git a/CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6d5b422a98..6d41459e4a 100644 --- a/CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -3,7 +3,7 @@ "pins": [ { "package": "Highlightr", - "repositoryURL": "https://github.com/raspu/Highlightr", + "repositoryURL": "https://github.com/raspu/Highlightr.git", "state": { "branch": null, "revision": "93199b9e434f04bda956a613af8f571933f9f037", diff --git a/CodeEdit/AppDelegate.swift b/CodeEdit/AppDelegate.swift index 00ebf9bf96..edef6ea027 100644 --- a/CodeEdit/AppDelegate.swift +++ b/CodeEdit/AppDelegate.swift @@ -30,9 +30,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } func applicationDidFinishLaunching(_ notification: Notification) { - if let appearanceString = UserDefaults.standard.string(forKey: Appearances.storageKey) { - Appearances(rawValue: appearanceString)?.applyAppearance() - } + AppPreferencesModel.shared.preferences.general.appAppearance.applyAppearance() +// if let appearanceString = UserDefaults.standard.string(forKey: Appearances.storageKey) { +// Appearances(rawValue: appearanceString)?.applyAppearance() +// } DispatchQueue.main.async { if NSApp.windows.isEmpty { @@ -76,8 +77,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } func handleOpen() { - let behavior = ReopenBehavior(rawValue: UserDefaults.standard.string(forKey: ReopenBehavior.storageKey) - ?? ReopenBehavior.default.rawValue) ?? ReopenBehavior.default + let behavior = AppPreferencesModel.shared.preferences.general.reopenBehavior +// let behavior = ReopenBehavior(rawValue: UserDefaults.standard.string(forKey: ReopenBehavior.storageKey) +// ?? ReopenBehavior.default.rawValue) ?? ReopenBehavior.default switch behavior { case .welcome: diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift index 44d17ce125..539d39666d 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift @@ -10,8 +10,9 @@ import WorkspaceClient import AppPreferences struct BreadcrumbsComponent: View { - @AppStorage(FileIconStyle.storageKey) - var iconStyle: FileIconStyle = .default + + @StateObject + private var prefs: AppPreferencesModel = .shared @ObservedObject var workspace: WorkspaceDocument @@ -51,7 +52,7 @@ struct BreadcrumbsComponent: View { .resizable() .aspectRatio(contentMode: .fit) .frame(width: 12) - .foregroundStyle(iconStyle == .color ? color : .secondary) + .foregroundStyle(prefs.preferences.general.fileIconStyle == .color ? color : .secondary) .onAppear { self.position = NSPoint( x: geometry.frame(in: .global).minX, diff --git a/CodeEdit/DefaultThemes/default-dark.json b/CodeEdit/DefaultThemes/default-dark.json index 7eaf710757..fa28e625e5 100644 --- a/CodeEdit/DefaultThemes/default-dark.json +++ b/CodeEdit/DefaultThemes/default-dark.json @@ -1,5 +1,5 @@ { - "darkTheme" : true, + "type" : "dark", "license" : "MIT", "author" : "CodeEdit", "description" : "CodeEdit default dark theme.", diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 4b5b2d0ec0..078e522c68 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - Placeholder + 91ab407a8c90efc5a78876ff070d1b9f9f760002 diff --git a/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorItem.swift b/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorItem.swift index e33e2f3f38..8cf13dfa41 100644 --- a/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorItem.swift +++ b/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorItem.swift @@ -16,8 +16,9 @@ import AppPreferences /// Displays a File or Folder in the ``ProjectNavigator``. /// struct ProjectNavigatorItem: View { - @AppStorage(FileIconStyle.storageKey) - var iconStyle: FileIconStyle = .default + + @StateObject + private var prefs: AppPreferencesModel = .shared /// The `FileItem` for this view var item: WorkspaceClient.FileItem @@ -93,6 +94,6 @@ struct ProjectNavigatorItem: View { /// Returns a color depending on the set icon color style in preferences private var iconColor: Color { - return iconStyle == .color ? item.iconColor : .secondary + return prefs.preferences.general.fileIconStyle == .color ? item.iconColor : .secondary } } diff --git a/CodeEdit/TabBar/TabBarItem.swift b/CodeEdit/TabBar/TabBarItem.swift index 42bb8a623b..6dce45bc37 100644 --- a/CodeEdit/TabBar/TabBarItem.swift +++ b/CodeEdit/TabBar/TabBarItem.swift @@ -30,8 +30,8 @@ struct TabBarItem: View { @Environment(\.colorScheme) var colorScheme - @AppStorage(FileIconStyle.storageKey) - private var iconStyle: FileIconStyle = .default + @StateObject + private var prefs: AppPreferencesModel = .shared @State var isHovering: Bool = false @@ -106,7 +106,7 @@ struct TabBarItem: View { .resizable() .aspectRatio(contentMode: .fit) .foregroundColor( - iconStyle == .color ? item.iconColor : .secondary + prefs.preferences.general.fileIconStyle == .color ? item.iconColor : .secondary ) .frame(width: 12, height: 12) Text(item.url.lastPathComponent) diff --git a/CodeEdit/Welcome/WelcomeView.swift b/CodeEdit/Welcome/WelcomeView.swift index bfb7409bf6..587de81cb8 100644 --- a/CodeEdit/Welcome/WelcomeView.swift +++ b/CodeEdit/Welcome/WelcomeView.swift @@ -21,8 +21,8 @@ struct WelcomeView: View { @State var isHoveringClose: Bool = false - @AppStorage(ReopenBehavior.storageKey) - var behavior: ReopenBehavior = .welcome + @StateObject + private var prefs: AppPreferencesModel = .shared var dismissWindow: () -> Void @@ -187,9 +187,9 @@ struct WelcomeView: View { HStack { Spacer() Toggle("Show this window when CodeEdit launches", isOn: .init(get: { - return self.behavior == .welcome + return prefs.preferences.general.reopenBehavior == .welcome }, set: { new in - self.behavior = new ? .welcome : .openPanel + prefs.preferences.general.reopenBehavior = new ? .welcome : .openPanel })) .toggleStyle(.checkbox) Spacer() diff --git a/CodeEditModules/Modules/AppPreferences/src/GeneralPreferences/PreferencesGeneralView.swift b/CodeEditModules/Modules/AppPreferences/src/GeneralPreferences/PreferencesGeneralView.swift new file mode 100644 index 0000000000..576568e93e --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/GeneralPreferences/PreferencesGeneralView.swift @@ -0,0 +1,56 @@ +// +// File.swift +// +// +// Created by Lukas Pistrol on 30.03.22. +// + +import SwiftUI +import Preferences + +public struct PreferencesGeneralView: View { + + @StateObject + private var prefs: AppPreferencesModel = .shared + + public init() {} + + public var body: some View { + Form { + Picker("Appearance:", selection: $prefs.preferences.general.appAppearance) { + Text("System") + .tag(AppPreferences.Appearances.system) + Divider() + Text("Light") + .tag(AppPreferences.Appearances.light) + Text("Dark") + .tag(AppPreferences.Appearances.dark) + } + .onChange(of: prefs.preferences.general.appAppearance) { tag in + tag.applyAppearance() + } + .fixedSize() + + Picker("File Icon Style:", selection: $prefs.preferences.general.fileIconStyle) { + Text("Color") + .tag(AppPreferences.FileIconStyle.color) + Text("Monochrome") + .tag(AppPreferences.FileIconStyle.monochrome) + } + .pickerStyle(.radioGroup) + + Picker("Reopen Behavior:", selection: $prefs.preferences.general.reopenBehavior) { + Text("Welcome Screen") + .tag(AppPreferences.ReopenBehavior.welcome) + Divider() + Text("Open Panel") + .tag(AppPreferences.ReopenBehavior.openPanel) + Text("New Document") + .tag(AppPreferences.ReopenBehavior.newDocument) + } + .fixedSize() + } + .frame(width: 844) + .padding(30) + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift new file mode 100644 index 0000000000..f3aa354920 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift @@ -0,0 +1,160 @@ +// +// File.swift +// +// +// Created by Lukas Pistrol on 01.04.22. +// + +import SwiftUI + +public struct AppPreferences: Codable { + + /// The general global setting + public var general: GeneralPreferences = .init() + + /// The global settings for themes + public var theme: ThemePreferences = .init() + + /// The global settings for the terminal emulator + public var terminal: TerminalPreferences = .init() + + /// The global settings for text editing + public var textEditing: TextEditingPreferences = .init() + +} + +public extension AppPreferences { + + /// The general global setting + struct GeneralPreferences: Codable { + + /// The appearance of the app + public var appAppearance: Appearances = .system + + /// The style for file icons + public var fileIconStyle: FileIconStyle = .color + + /// The reopen behavior of the app + public var reopenBehavior: ReopenBehavior = .welcome + } + + /// The appearance of the app + /// - **system**: uses the system appearance + /// - **dark**: always uses dark appearance + /// - **light**: always uses light appearance + enum Appearances: String, Codable { + case system + case light + case dark + + /// Applies the selected appearance + public func applyAppearance() { + switch self { + case .system: + NSApp.appearance = nil + + case .dark: + NSApp.appearance = .init(named: .darkAqua) + + case .light: + NSApp.appearance = .init(named: .aqua) + } + } + } + + /// The style for file icons + /// - **color**: File icons appear in their default colors + /// - **monochrome**: File icons appear monochromatic + enum FileIconStyle: String, Codable { + case color + case monochrome + } + + /// The reopen behavior of the app + /// - **welcome**: On restart the app will show the welcome screen + /// - **openPanel**: On restart the app will show an open panel + /// - **newDocument**: On restart a new empty document will be created + enum ReopenBehavior: String, Codable { + case welcome + case openPanel + case newDocument + } + +} + +public extension AppPreferences { + + /// A dictionary containing the keys and associated ``Theme/Attributes`` of overridden properties + typealias ThemeOverrides = [String: Theme.Attributes] + + /// The global settings for themes + struct ThemePreferences: Codable { + + /// The name of the currently selected theme + public var selectedTheme: String? + + /// Use the system background that matches the appearance setting + public var useThemeBackground: Bool = false + + /// Dictionary of themes containing overrides + /// + /// ```json + /// "overrides" : { + /// "DefaultDark" : { + /// "background" : { + /// "color" : "#123456" + /// }, + /// ... + /// }, + /// ... + /// }, + /// ``` + public var overrides: [String: ThemeOverrides] = [:] + } + +} + +public extension AppPreferences { + + /// The global settings for the terminal emulator + struct TerminalPreferences: Codable { + /// If true terminal appearance will always be `dark`. Otherwise it adapts to the system setting. + public var darkAppearance: Bool = false + + /// The selected shell to use. + public var shell: TerminalShell = .system + + /// The font to use in terminal. + public var font: TerminalFont = .init() + } + + /// The shell options. + /// - **bash**: uses the default bash shell + /// - **zsh**: uses the ZSH shell + /// - **system**: uses the system default shell (most likely ZSH) + enum TerminalShell: String, Codable { + case bash + case zsh + case system + } + + struct TerminalFont: Codable { + /// Indicates whether or not to use a custom font + public var customFont: Bool = false + + /// The font size for the custom font + public var size: Int? + + /// The name of the custom font + public var name: String? + } +} + +public extension AppPreferences { + + /// The global settings for text editing + struct TextEditingPreferences: Codable { + /// An integer indicating how many spaces a `tab` will generate + public var defaultTabWidth: Int = 4 + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift new file mode 100644 index 0000000000..dd1a9d2d74 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift @@ -0,0 +1,55 @@ +// +// File.swift +// +// +// Created by Lukas Pistrol on 01.04.22. +// + +import Foundation +import SwiftUI + +public class AppPreferencesModel: ObservableObject { + + public static let shared: AppPreferencesModel = .init() + + private init() { + self.preferences = loadPreferences() + } + + @Published + public var preferences: AppPreferences! { + didSet { + try? savePreferences() + objectWillChange.send() + } + } + + private func loadPreferences() -> AppPreferences { + if !filemanager.fileExists(atPath: baseURL.path) { + return .init() + } + + guard let json = try? Data(contentsOf: baseURL), + let prefs = try? JSONDecoder().decode(AppPreferences.self, from: json) + else { + return .init() + } + return prefs + } + + private func savePreferences() throws { + let data = try JSONEncoder().encode(preferences) + let json = try JSONSerialization.jsonObject(with: data) + let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) + try prettyJSON.write(to: baseURL, options: .atomic) + } + + public let filemanager = FileManager.default + public var baseURL: URL { + return filemanager + .homeDirectoryForCurrentUser + .appendingPathComponent(".codeedit", isDirectory: true) + .appendingPathComponent("preferences") + .appendingPathExtension("json") + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/Appearances.swift b/CodeEditModules/Modules/AppPreferences/src/Model/Appearances.swift deleted file mode 100644 index 035e5c8c06..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/Model/Appearances.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Appearances.swift -// CodeEdit -// -// Created by Pavel Kasila on 17.03.22. -// - -import SwiftUI - -public enum Appearances: String, CaseIterable, Hashable { - case system - case light - case dark - - public func applyAppearance() { - switch self { - case .system: - NSApp.appearance = nil - - case .dark: - NSApp.appearance = .init(named: .darkAqua) - - case .light: - NSApp.appearance = .init(named: .aqua) - } - } - - public static let `default` = Appearances.system - public static let storageKey = "appearance" -} diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/FileIconStyle.swift b/CodeEditModules/Modules/AppPreferences/src/Model/FileIconStyle.swift deleted file mode 100644 index b72c081c3f..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/Model/FileIconStyle.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// FileIconStyle.swift -// CodeEdit -// -// Created by Lukas Pistrol on 18.03.22. -// - -import Foundation - -public enum FileIconStyle: String, CaseIterable, Hashable { - case color - case monochrome - - public static let `default` = FileIconStyle.color - public static let storageKey = "fileIconStyle" -} diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/ReopenBehavior.swift b/CodeEditModules/Modules/AppPreferences/src/Model/ReopenBehavior.swift deleted file mode 100644 index 93f1283d19..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/Model/ReopenBehavior.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// ReopenBehavior.swift -// CodeEdit -// -// Created by Pavel Kasila on 17.03.22. -// - -import Foundation - -public enum ReopenBehavior: String, CaseIterable, Hashable { - case welcome - case openPanel - case newDocument - - public static let `default` = ReopenBehavior.welcome - public static let storageKey = "behavior" -} diff --git a/CodeEditModules/Modules/AppPreferences/src/PreferencesGeneralView.swift b/CodeEditModules/Modules/AppPreferences/src/PreferencesGeneralView.swift deleted file mode 100644 index ea96370cc3..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/PreferencesGeneralView.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// File.swift -// -// -// Created by Lukas Pistrol on 30.03.22. -// - -import SwiftUI -import Preferences - -public struct PreferencesGeneralView: View { - - @AppStorage(Appearances.storageKey) - private var appearance: Appearances = .default - - @AppStorage(ReopenBehavior.storageKey) - private var reopenBehavior: ReopenBehavior = .default - - @AppStorage(FileIconStyle.storageKey) - private var fileIconStyle: FileIconStyle = .default - - public init() {} - - public var body: some View { - Form { - Picker("Appearance:", selection: $appearance) { - Text("System") - .tag(Appearances.system) - Divider() - Text("Light") - .tag(Appearances.light) - Text("Dark") - .tag(Appearances.dark) - } - .onChange(of: appearance) { tag in - tag.applyAppearance() - } - .fixedSize() - - Picker("File Icon Style:", selection: $fileIconStyle) { - Text("Color") - .tag(FileIconStyle.color) - Text("Monochrome") - .tag(FileIconStyle.monochrome) - } - .pickerStyle(.radioGroup) - - Picker("Reopen Behavior:", selection: $reopenBehavior) { - Text("Welcome Screen") - .tag(ReopenBehavior.welcome) - Divider() - Text("Open Panel") - .tag(ReopenBehavior.openPanel) - Text("New Document") - .tag(ReopenBehavior.newDocument) - } - .fixedSize() - } - .frame(width: 844) - .padding(30) - } -} diff --git a/CodeEditModules/Modules/AppPreferences/src/PreferencesTextEditingView.swift b/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift similarity index 50% rename from CodeEditModules/Modules/AppPreferences/src/PreferencesTextEditingView.swift rename to CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift index e31890782b..d29c0cbff3 100644 --- a/CodeEditModules/Modules/AppPreferences/src/PreferencesTextEditingView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift @@ -6,20 +6,21 @@ // import SwiftUI -import CodeFile public struct PreferencesTextEditingView: View { - @AppStorage(EditorTabWidth.storageKey) - var defaultTabWidth: Int = EditorTabWidth.default + @StateObject + private var prefs: AppPreferencesModel = .shared public init() {} public var body: some View { Form { HStack { - Stepper("Default Tab Width:", value: $defaultTabWidth, in: 2...8) - Text(String(defaultTabWidth)) + Stepper("Default Tab Width:", + value: $prefs.preferences.textEditing.defaultTabWidth, + in: 2...8) + Text(String(prefs.preferences.textEditing.defaultTabWidth)) } } .frame(width: 844) diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift index e3e01f6e41..b0aae05f53 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -11,7 +11,8 @@ import SwiftUI public struct Theme: Identifiable, Codable, Equatable { enum CodingKeys: String, CodingKey { - case author, license, distributionURL, name, darkTheme, editor, terminal, version + case author, license, distributionURL, name, editor, terminal, version + case appearance = "type" case metadataDescription = "description" } @@ -27,7 +28,7 @@ public struct Theme: Identifiable, Codable, Equatable { public var distributionURL: String public var name: String public var version: String - public var darkTheme: Bool + public var appearance: ThemeType public var editor: EditorColors public var terminal: TerminalColors @@ -40,7 +41,7 @@ public struct Theme: Identifiable, Codable, Equatable { metadataDescription: String, distributionURL: String, name: String, - darkTheme: Bool, + appearance: ThemeType, version: String ) { self.author = author @@ -48,174 +49,187 @@ public struct Theme: Identifiable, Codable, Equatable { self.metadataDescription = metadataDescription self.distributionURL = distributionURL self.name = name - self.darkTheme = darkTheme + self.appearance = appearance self.version = version self.editor = editor self.terminal = terminal } } -// MARK: - Attributes -public struct Attributes: Codable { - public var color: String - - public init(color: String) { - self.color = color +public extension Theme { + enum ThemeType: String, Codable { + case dark + case light } +} - public internal(set) var swiftColor: Color { - get { - Color(hex: color) +// MARK: - Attributes +public extension Theme { + struct Attributes: Codable { + public var color: String + + public init(color: String) { + self.color = color } - set { - self.color = newValue.hexString + + public internal(set) var swiftColor: Color { + get { + Color(hex: color) + } + set { + self.color = newValue.hexString + } } } } -public struct EditorColors: Codable { - public var text: Attributes - public var insertionPoint: Attributes - public var invisibles: Attributes - public var background: Attributes - public var lineHighlight: Attributes - public var selection: Attributes - public var keywords: Attributes - public var commands: Attributes - public var types: Attributes - public var attributes: Attributes - public var variables: Attributes - public var values: Attributes - public var numbers: Attributes - public var strings: Attributes - public var characters: Attributes - public var comments: Attributes - - public init( - text: Attributes, - insertionPoint: Attributes, - invisibles: Attributes, - background: Attributes, - lineHighlight: Attributes, - selection: Attributes, - keywords: Attributes, - commands: Attributes, - types: Attributes, - attributes: Attributes, - variables: Attributes, - values: Attributes, - numbers: Attributes, - strings: Attributes, - characters: Attributes, - comments: Attributes - ) { - self.text = text - self.insertionPoint = insertionPoint - self.invisibles = invisibles - self.background = background - self.lineHighlight = lineHighlight - self.selection = selection - self.keywords = keywords - self.commands = commands - self.types = types - self.attributes = attributes - self.variables = variables - self.values = values - self.numbers = numbers - self.strings = strings - self.characters = characters - self.comments = comments +public extension Theme { + struct EditorColors: Codable { + public var text: Attributes + public var insertionPoint: Attributes + public var invisibles: Attributes + public var background: Attributes + public var lineHighlight: Attributes + public var selection: Attributes + public var keywords: Attributes + public var commands: Attributes + public var types: Attributes + public var attributes: Attributes + public var variables: Attributes + public var values: Attributes + public var numbers: Attributes + public var strings: Attributes + public var characters: Attributes + public var comments: Attributes + + public init( + text: Attributes, + insertionPoint: Attributes, + invisibles: Attributes, + background: Attributes, + lineHighlight: Attributes, + selection: Attributes, + keywords: Attributes, + commands: Attributes, + types: Attributes, + attributes: Attributes, + variables: Attributes, + values: Attributes, + numbers: Attributes, + strings: Attributes, + characters: Attributes, + comments: Attributes + ) { + self.text = text + self.insertionPoint = insertionPoint + self.invisibles = invisibles + self.background = background + self.lineHighlight = lineHighlight + self.selection = selection + self.keywords = keywords + self.commands = commands + self.types = types + self.attributes = attributes + self.variables = variables + self.values = values + self.numbers = numbers + self.strings = strings + self.characters = characters + self.comments = comments + } } } -public struct TerminalColors: Codable { - - public var text: Attributes - public var boldText: Attributes - public var cursor: Attributes - public var background: Attributes - public var selection: Attributes - public var black: Attributes - public var red: Attributes - public var green: Attributes - public var yellow: Attributes - public var blue: Attributes - public var magenta: Attributes - public var cyan: Attributes - public var white: Attributes - public var brightBlack: Attributes - public var brightRed: Attributes - public var brightGreen: Attributes - public var brightYellow: Attributes - public var brightBlue: Attributes - public var brightMagenta: Attributes - public var brightCyan: Attributes - public var brightWhite: Attributes - - public var ansiColors: [String] { - [ - black.color, - red.color, - green.color, - yellow.color, - blue.color, - magenta.color, - cyan.color, - white.color, - brightBlack.color, - brightRed.color, - brightGreen.color, - brightYellow.color, - brightBlue.color, - brightMagenta.color, - brightCyan.color, - brightWhite.color, - ] - } +public extension Theme { + struct TerminalColors: Codable { + + public var text: Attributes + public var boldText: Attributes + public var cursor: Attributes + public var background: Attributes + public var selection: Attributes + public var black: Attributes + public var red: Attributes + public var green: Attributes + public var yellow: Attributes + public var blue: Attributes + public var magenta: Attributes + public var cyan: Attributes + public var white: Attributes + public var brightBlack: Attributes + public var brightRed: Attributes + public var brightGreen: Attributes + public var brightYellow: Attributes + public var brightBlue: Attributes + public var brightMagenta: Attributes + public var brightCyan: Attributes + public var brightWhite: Attributes + + public var ansiColors: [String] { + [ + black.color, + red.color, + green.color, + yellow.color, + blue.color, + magenta.color, + cyan.color, + white.color, + brightBlack.color, + brightRed.color, + brightGreen.color, + brightYellow.color, + brightBlue.color, + brightMagenta.color, + brightCyan.color, + brightWhite.color, + ] + } - init( - text: Attributes, - boldText: Attributes, - cursor: Attributes, - background: Attributes, - selection: Attributes, - black: Attributes, - red: Attributes, - green: Attributes, - yellow: Attributes, - blue: Attributes, - magenta: Attributes, - cyan: Attributes, - white: Attributes, - brightBlack: Attributes, - brightRed: Attributes, - brightGreen: Attributes, - brightYellow: Attributes, - brightBlue: Attributes, - brightMagenta: Attributes, - brightCyan: Attributes, - brightWhite: Attributes - ) { - self.text = text - self.boldText = boldText - self.cursor = cursor - self.background = background - self.selection = selection - self.black = black - self.red = red - self.green = green - self.yellow = yellow - self.blue = blue - self.magenta = magenta - self.cyan = cyan - self.white = white - self.brightBlack = brightBlack - self.brightRed = brightRed - self.brightGreen = brightGreen - self.brightYellow = brightYellow - self.brightBlue = brightBlue - self.brightMagenta = brightMagenta - self.brightCyan = brightCyan - self.brightWhite = brightWhite + init( + text: Attributes, + boldText: Attributes, + cursor: Attributes, + background: Attributes, + selection: Attributes, + black: Attributes, + red: Attributes, + green: Attributes, + yellow: Attributes, + blue: Attributes, + magenta: Attributes, + cyan: Attributes, + white: Attributes, + brightBlack: Attributes, + brightRed: Attributes, + brightGreen: Attributes, + brightYellow: Attributes, + brightBlue: Attributes, + brightMagenta: Attributes, + brightCyan: Attributes, + brightWhite: Attributes + ) { + self.text = text + self.boldText = boldText + self.cursor = cursor + self.background = background + self.selection = selection + self.black = black + self.red = red + self.green = green + self.yellow = yellow + self.blue = blue + self.magenta = magenta + self.cyan = cyan + self.white = white + self.brightBlack = brightBlack + self.brightRed = brightRed + self.brightGreen = brightGreen + self.brightYellow = brightYellow + self.brightBlue = brightBlue + self.brightMagenta = brightMagenta + self.brightCyan = brightCyan + self.brightWhite = brightWhite + } } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 2c6a7b2535..25154ca593 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -29,11 +29,11 @@ public class ThemeModel: ObservableObject { public var selectedTheme: Theme? public var darkThemes: [Theme] { - themes.filter { $0.darkTheme } + themes.filter { $0.appearance == .dark } } public var lightThemes: [Theme] { - themes.filter { !$0.darkTheme } + themes.filter { $0.appearance == .light } } init() { diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index fbe06125f0..72a23c5aea 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -5,7 +5,6 @@ // Created by Lukas Pistrol on 30.03.22. // -import CodeFile import FontPicker import SwiftUI import Preferences diff --git a/CodeEditModules/Modules/CodeFile/src/Model/EditorTabWidth.swift b/CodeEditModules/Modules/CodeFile/src/Model/EditorTabWidth.swift deleted file mode 100644 index de10a136ed..0000000000 --- a/CodeEditModules/Modules/CodeFile/src/Model/EditorTabWidth.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -public enum EditorTabWidth { - static public let `default`: Int = 4 - static public let storageKey = "defaultTabWidth" -} diff --git a/CodeEditModules/Modules/CodeFile/src/TextView/CodeEditorTextView.swift b/CodeEditModules/Modules/CodeFile/src/TextView/CodeEditorTextView.swift index f805527c3a..fe4ef91155 100644 --- a/CodeEditModules/Modules/CodeFile/src/TextView/CodeEditorTextView.swift +++ b/CodeEditModules/Modules/CodeFile/src/TextView/CodeEditorTextView.swift @@ -7,10 +7,12 @@ import Cocoa import SwiftUI +import AppPreferences public class CodeEditorTextView: NSTextView { - @AppStorage(EditorTabWidth.storageKey) - private var tabWidth: Int = EditorTabWidth.default + + @StateObject + private var prefs: AppPreferencesModel = .shared init( textContainer container: NSTextContainer? @@ -84,7 +86,7 @@ public class CodeEditorTextView: NSTextView { super.insertText( String( repeating: " ", - count: tabWidth + count: prefs.preferences.textEditing.defaultTabWidth ), replacementRange: selectedRange() ) diff --git a/CodeEditModules/Modules/StatusBar/src/StatusBarItems/StatusBarIndentSelector.swift b/CodeEditModules/Modules/StatusBar/src/StatusBarItems/StatusBarIndentSelector.swift index b9b9c1bd9a..5529fa322a 100644 --- a/CodeEditModules/Modules/StatusBar/src/StatusBarItems/StatusBarIndentSelector.swift +++ b/CodeEditModules/Modules/StatusBar/src/StatusBarItems/StatusBarIndentSelector.swift @@ -5,6 +5,7 @@ // Created by Lukas Pistrol on 22.03.22. // +import AppPreferences import SwiftUI import CodeFile @@ -13,8 +14,8 @@ internal struct StatusBarIndentSelector: View { @ObservedObject private var model: StatusBarModel - @AppStorage(EditorTabWidth.storageKey) - private var tabWidth: Int = EditorTabWidth.default + @StateObject + private var prefs: AppPreferencesModel = .shared internal init(model: StatusBarModel) { self.model = model @@ -32,14 +33,14 @@ internal struct StatusBarIndentSelector: View { Divider() - Picker("Tab Width", selection: $tabWidth) { + Picker("Tab Width", selection: $prefs.preferences.textEditing.defaultTabWidth) { ForEach(2..<9) { index in Text("\(index) Spaces") .tag(index) } } } label: { - Text("\(tabWidth) Spaces") + Text("\(prefs.preferences.textEditing.defaultTabWidth) Spaces") .font(model.toolbarFont) } .menuStyle(.borderlessButton) diff --git a/CodeEditModules/Package.swift b/CodeEditModules/Package.swift index b57e08593f..8672fd84d3 100644 --- a/CodeEditModules/Package.swift +++ b/CodeEditModules/Package.swift @@ -92,6 +92,7 @@ let package = Package( name: "CodeFile", dependencies: [ "Highlightr", + "AppPreferences" ], path: "Modules/CodeFile/src" ), @@ -172,11 +173,9 @@ let package = Package( name: "AppPreferences", dependencies: [ "Preferences", - "CodeFile", "FontPicker", ], - path: "Modules/AppPreferences/src", - resources: [.copy("Modules/AppPreferences/resources")] + path: "Modules/AppPreferences/src" ) ] ) From 4be1ee12b56ac8c3d9938c3fd209f54dc2dcd702 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Fri, 1 Apr 2022 10:18:22 +0200 Subject: [PATCH 11/26] create `~/.codeedit/`directory if non-existent --- CodeEdit/Info.plist | 2 +- .../AppPreferences/src/Model/AppPreferencesModel.swift | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 078e522c68..7b0408647e 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 91ab407a8c90efc5a78876ff070d1b9f9f760002 + c5fe3e6719b2bfb3ccf0e2d7dfa6955f4e8b3494 diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift index dd1a9d2d74..ae3ea674b3 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift @@ -26,6 +26,10 @@ public class AppPreferencesModel: ObservableObject { private func loadPreferences() -> AppPreferences { if !filemanager.fileExists(atPath: baseURL.path) { + let codeEditURL = filemanager + .homeDirectoryForCurrentUser + .appendingPathComponent(".codeedit", isDirectory: true) + try? filemanager.createDirectory(at: codeEditURL, withIntermediateDirectories: false) return .init() } From 79311f8f93dae32ec3305599311fa20d4fd6615e Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Fri, 1 Apr 2022 12:22:14 +0200 Subject: [PATCH 12/26] implemented remaining terminal settings to `~/.codeedit/preferences.json` --- CodeEdit/Info.plist | 2 +- .../src/Model/AppPreferences.swift | 4 +- .../src/Model/AppPreferencesModel.swift | 4 +- .../ThemePreferences/EditorThemeView.swift | 5 +- .../Model/TerminalColorScheme.swift | 17 ------ .../ThemePreferences/Model/TerminalFont.swift | 26 --------- .../Model/TerminalShellType.swift | 17 ------ .../ThemePreferences/TerminalThemeView.swift | 53 ++++++------------- .../src/TerminalEmulatorView.swift | 34 +++++------- 9 files changed, 38 insertions(+), 124 deletions(-) delete mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalColorScheme.swift delete mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalFont.swift delete mode 100644 CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalShellType.swift diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 7b0408647e..748bb6d66b 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - c5fe3e6719b2bfb3ccf0e2d7dfa6955f4e8b3494 + 4be1ee12b56ac8c3d9938c3fd209f54dc2dcd702 diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift index f3aa354920..522f4ff086 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift @@ -143,10 +143,10 @@ public extension AppPreferences { public var customFont: Bool = false /// The font size for the custom font - public var size: Int? + public var size: Int = 11 /// The name of the custom font - public var name: String? + public var name: String = "SF-MonoMedium" } } diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift index ae3ea674b3..f77a597140 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift @@ -19,7 +19,9 @@ public class AppPreferencesModel: ObservableObject { @Published public var preferences: AppPreferences! { didSet { - try? savePreferences() + // swiftlint:disable force_try + print("did change") + try! savePreferences() objectWillChange.send() } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift index 175980814f..1991150348 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift @@ -12,6 +12,9 @@ struct EditorThemeView: View { @StateObject private var themeModel: ThemeModel = .shared + @StateObject + private var prefs: AppPreferencesModel = .shared + var body: some View { ZStack(alignment: .topLeading) { Rectangle() @@ -19,7 +22,7 @@ struct EditorThemeView: View { if let selectedTheme = themeModel.selectedTheme, let index = themeModel.themes.firstIndex(of: selectedTheme) { VStack(alignment: .leading, spacing: 0) { - Toggle("Use theme background ", isOn: .constant(true)) + Toggle("Use theme background ", isOn: $prefs.preferences.theme.useThemeBackground) .padding(.bottom, 20) HStack(spacing: 0) { VStack(alignment: .leading, spacing: 10) { diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalColorScheme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalColorScheme.swift deleted file mode 100644 index c4b10e1ac8..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalColorScheme.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// File.swift -// -// -// Created by Lukas Pistrol on 26.03.22. -// - -import Foundation - -public enum TerminalColorScheme: String, CaseIterable, Hashable { - case auto - case light - case dark - - static public let `default` = TerminalColorScheme.auto - static public let storageKey = "terminalColorScheme" -} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalFont.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalFont.swift deleted file mode 100644 index 78a19d10d1..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalFont.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// File.swift -// -// -// Created by Lukas Pistrol on 23.03.22. -// - -import Foundation - -public enum TerminalFont: String, CaseIterable, Hashable { - case systemFont - case custom - - static public let `default`: TerminalFont = .systemFont - static public let storageKey = "terminalFontSelection" -} - -public enum TerminalFontName { - static public let `default`: String = "SFMono-Medium" - static public let storageKey = "terminalFontName" -} - -public enum TerminalFontSize { - static public let `default`: Int = 11 - static public let storageKey = "terminalFontSize" -} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalShellType.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalShellType.swift deleted file mode 100644 index 940732056b..0000000000 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/TerminalShellType.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// TerminalShellType.swift -// CodeEdit -// -// Created by Lukas Pistrol on 22.03.22. -// - -import Foundation - -public enum TerminalShellType: String, CaseIterable, Hashable { - case bash - case zsh - case auto - - static public let `default` = TerminalShellType.auto - static public let storageKey = "terminalShellType" -} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift index c1a1d7eee0..f5601b0467 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift @@ -9,29 +9,13 @@ import SwiftUI import FontPicker struct TerminalThemeView: View { - @AppStorage(TerminalShellType.storageKey) - var shellType: TerminalShellType = .default - - @AppStorage(TerminalFont.storageKey) - var terminalFontSelection: TerminalFont = .custom - - @AppStorage(TerminalFontName.storageKey) - var terminalFontName: String = TerminalFontName.default - - @AppStorage(TerminalFontSize.storageKey) - var terminalFontSize: Int = TerminalFontSize.default - - @AppStorage(TerminalColorScheme.storageKey) - var terminalColorSchmeme: TerminalColorScheme = .default + @StateObject + private var prefs: AppPreferencesModel = .shared @StateObject private var themeModel: ThemeModel = .shared - @State private var darkTerminal: Bool = false - - init() { - self._darkTerminal = .init(initialValue: terminalColorSchmeme == .dark) - } + init() {} var body: some View { ZStack(alignment: .topLeading) { @@ -46,18 +30,11 @@ struct TerminalThemeView: View { } .padding(20) } - .onChange(of: darkTerminal) { newValue in - if newValue { - terminalColorSchmeme = .dark - } else { - terminalColorSchmeme = .auto - } - } } private var topToggles: some View { VStack(alignment: .leading) { - Toggle("Use dark terminal appearance", isOn: $darkTerminal) + Toggle("Use dark terminal appearance", isOn: $prefs.preferences.terminal.darkAppearance) } } @@ -67,14 +44,14 @@ struct TerminalThemeView: View { .font(.system(size: 13, weight: .semibold)) .foregroundColor(.secondary) .padding(.bottom, 5) - Picker("Terminal Shell", selection: $shellType) { + Picker("Terminal Shell", selection: $prefs.preferences.terminal.shell) { Text("System Default") - .tag(TerminalShellType.auto) + .tag(AppPreferences.TerminalShell.system) Divider() Text("ZSH") - .tag(TerminalShellType.zsh) + .tag(AppPreferences.TerminalShell.zsh) Text("Bash") - .tag(TerminalShellType.bash) + .tag(AppPreferences.TerminalShell.bash) } .fixedSize() .labelsHidden() @@ -88,17 +65,19 @@ struct TerminalThemeView: View { .foregroundColor(.secondary) .padding(.bottom, 5) HStack { - Picker("Terminal Font", selection: $terminalFontSelection) { + Picker("Terminal Font", selection: $prefs.preferences.terminal.font.customFont) { Text("System Font") - .tag(TerminalFont.systemFont) + .tag(false) Text("Custom") - .tag(TerminalFont.custom) + .tag(true) } .fixedSize() .labelsHidden() - if terminalFontSelection == .custom { - FontPicker("\(terminalFontName) \(terminalFontSize)", - name: $terminalFontName, size: $terminalFontSize) + if prefs.preferences.terminal.font.customFont { + FontPicker( + "\(prefs.preferences.terminal.font.name) \(prefs.preferences.terminal.font.size)", + name: $prefs.preferences.terminal.font.name, size: $prefs.preferences.terminal.font.size + ) } } } diff --git a/CodeEditModules/Modules/TerminalEmulator/src/TerminalEmulatorView.swift b/CodeEditModules/Modules/TerminalEmulator/src/TerminalEmulatorView.swift index c96e84ef85..8435002430 100644 --- a/CodeEditModules/Modules/TerminalEmulator/src/TerminalEmulatorView.swift +++ b/CodeEditModules/Modules/TerminalEmulator/src/TerminalEmulatorView.swift @@ -17,20 +17,8 @@ import AppPreferences /// for use in SwiftUI. /// public struct TerminalEmulatorView: NSViewRepresentable { - @AppStorage(TerminalShellType.storageKey) - var shellType: TerminalShellType = .default - - @AppStorage(TerminalFont.storageKey) - var terminalFontSelection: TerminalFont = .default - - @AppStorage(TerminalFontName.storageKey) - var terminalFontName: String = TerminalFontName.default - - @AppStorage(TerminalFontSize.storageKey) - var terminalFontSize: Int = TerminalFontSize.default - - @AppStorage(TerminalColorScheme.storageKey) - var terminalColorSchmeme: TerminalColorScheme = .default + @StateObject + private var prefs: AppPreferencesModel = .shared @StateObject private var themeModel: ThemeModel = .shared @@ -43,10 +31,13 @@ public struct TerminalEmulatorView: NSViewRepresentable { private let systemFont: NSFont = .monospacedSystemFont(ofSize: 11, weight: .medium) private var font: NSFont { - if terminalFontSelection == .systemFont { + if !prefs.preferences.terminal.font.customFont { return systemFont } - return NSFont(name: terminalFontName, size: CGFloat(terminalFontSize)) ?? systemFont + return NSFont( + name: prefs.preferences.terminal.font.name, + size: CGFloat(prefs.preferences.terminal.font.size) + ) ?? systemFont } private var url: URL @@ -73,8 +64,8 @@ public struct TerminalEmulatorView: NSViewRepresentable { /// return String(cString: pwd.pw_shell) /// ``` private func getShell() -> String { - switch shellType { - case .auto: + switch prefs.preferences.terminal.shell { + case .system: return autoDetectDefaultShell() case .bash: return "/bin/bash" @@ -112,11 +103,10 @@ public struct TerminalEmulatorView: NSViewRepresentable { /// returns a `NSAppearance` based on the user setting of the terminal appearance, /// `nil` if app default is not overriden private var colorAppearance: NSAppearance? { - switch terminalColorSchmeme { - case .auto: return nil - case .light: return .init(named: .aqua) - case .dark: return .init(named: .darkAqua) + if prefs.preferences.terminal.darkAppearance { + return .init(named: .darkAqua) } + return nil } /// Inherited from NSViewRepresentable.makeNSView(context:). From ffac43fbefb1a1d0b215e8dc7998053ac00a79cc Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Fri, 1 Apr 2022 13:15:19 +0200 Subject: [PATCH 13/26] documentation added --- CodeEdit/Info.plist | 2 +- .../src/Model/AppPreferences.swift | 8 ++++ .../src/Model/AppPreferencesModel.swift | 28 +++++++++--- .../src/ThemePreferences/Model/Theme.swift | 36 ++++++++++++++- .../ThemePreferences/Model/ThemeModel.swift | 45 +++++++++++++++++-- 5 files changed, 108 insertions(+), 11 deletions(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 748bb6d66b..5c82c3ed81 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 4be1ee12b56ac8c3d9938c3fd209f54dc2dcd702 + 79311f8f93dae32ec3305599311fa20d4fd6615e diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift index 522f4ff086..65e8f1c872 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift @@ -7,6 +7,14 @@ import SwiftUI +/// # AppPreferences +/// +/// The model structure of settings for `CodeEdit` +/// +/// A `JSON` representation is persisted in `~/.codeedit/preference.json`. +/// - Attention: Don't use `UserDefaults` for persisting user accessible settings. +/// If a further setting is needed, extend the struct like ``GeneralPreferences``, +/// ``ThemePreferences``, or ``TerminalPreferences`` does. public struct AppPreferences: Codable { /// The general global setting diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift index f77a597140..516bb976fd 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift @@ -8,24 +8,35 @@ import Foundation import SwiftUI +/// The Preferences View Model. Accessible via the singleton "``AppPreferencesModel/shared``". +/// +/// **Usage:** +/// ```swift +/// @StateObject +/// private var prefs: AppPreferencesModel = .shared +/// ``` public class AppPreferencesModel: ObservableObject { + /// The publicly available singleton instance of ``AppPreferencesModel`` public static let shared: AppPreferencesModel = .init() private init() { self.preferences = loadPreferences() } + /// Published instance of the ``AppPreferences`` model. + /// + /// Changes are saved automatically. @Published public var preferences: AppPreferences! { didSet { - // swiftlint:disable force_try - print("did change") - try! savePreferences() + try? savePreferences() objectWillChange.send() } } + /// Load and construct ``AppPreferences`` model from + /// `~/.codeedit/preferences.json` private func loadPreferences() -> AppPreferences { if !filemanager.fileExists(atPath: baseURL.path) { let codeEditURL = filemanager @@ -43,6 +54,8 @@ public class AppPreferencesModel: ObservableObject { return prefs } + /// Save``AppPreferences`` model to + /// `~/.codeedit/preferences.json` private func savePreferences() throws { let data = try JSONEncoder().encode(preferences) let json = try JSONSerialization.jsonObject(with: data) @@ -50,8 +63,13 @@ public class AppPreferencesModel: ObservableObject { try prettyJSON.write(to: baseURL, options: .atomic) } - public let filemanager = FileManager.default - public var baseURL: URL { + /// Default instance of the `FileManager` + private let filemanager = FileManager.default + + /// The URL of the `preferences.json` settings file. + /// + /// `~/.codeedit/preferences.json` + private var baseURL: URL { return filemanager .homeDirectoryForCurrentUser .appendingPathComponent(".codeedit", isDirectory: true) diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift index b0aae05f53..f35d1ed1e4 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -7,7 +7,9 @@ import SwiftUI -// MARK: - Theme +/// # Theme +/// +/// The model structure of themes for the editor & terminal emulator public struct Theme: Identifiable, Codable, Equatable { enum CodingKeys: String, CodingKey { @@ -20,17 +22,36 @@ public struct Theme: Identifiable, Codable, Equatable { lhs.id == rhs.id } + /// The `id` of the theme public var id: String { self.name } + /// The `author` of the theme public var author: String + + /// The `licence` of the theme public var license: String + + /// A short `description` of the theme public var metadataDescription: String + + /// An URL for reference public var distributionURL: String + + /// The `display name` of the theme public var name: String + + /// The `version` of the theme public var version: String + + /// The ``ThemeType`` of the theme + /// + /// Appears as `"type"` in the `preferences.json` public var appearance: ThemeType + /// Editor colors of the theme public var editor: EditorColors + + /// Terminal colors of the theme public var terminal: TerminalColors public init( @@ -57,6 +78,9 @@ public struct Theme: Identifiable, Codable, Equatable { } public extension Theme { + /// The type of the theme + /// - **dark**: this is a theme for dark system appearance + /// - **light**: this is a theme for light system appearance enum ThemeType: String, Codable { case dark case light @@ -65,13 +89,20 @@ public extension Theme { // MARK: - Attributes public extension Theme { + /// Attributes of a certain field + /// + /// As of now it only includes the colors `hex` string and + /// an accessor for a `SwiftUI` `Color`. struct Attributes: Codable { + + /// The 24-bit hex string of the color (e.g. #123456) public var color: String public init(color: String) { self.color = color } + /// The `SwiftUI` color public internal(set) var swiftColor: Color { get { Color(hex: color) @@ -84,6 +115,7 @@ public extension Theme { } public extension Theme { + /// The editor colors of the theme struct EditorColors: Codable { public var text: Attributes public var insertionPoint: Attributes @@ -141,8 +173,8 @@ public extension Theme { } public extension Theme { + /// The terminal emulator colors of the theme struct TerminalColors: Codable { - public var text: Attributes public var boldText: Attributes public var cursor: Attributes diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 25154ca593..f77ddb8990 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -7,36 +7,58 @@ import SwiftUI +/// The Theme View Model. Accessible via the singleton "``ThemeModel/shared``". +/// +/// **Usage:** +/// ```swift +/// @StateObject +/// private var themeModel: ThemeModel = .shared +/// ``` public class ThemeModel: ObservableObject { public static let shared: ThemeModel = .init() + /// The selected appearance in the sidebar. + /// - **0**: dark mode themes + /// - **1**: light mode themes @Published var selectedAppearance: Int = 0 + + /// The selected tab in the main section. + /// - **0**: Preview + /// - **1**: Editor + /// - **2**: Terminal @Published var selectedTab: Int = 2 + /// An array of loaded ``Theme``. @Published public var themes: [Theme] = [] { didSet { + // TODO: Don't overwrite themes + // Instead save changed values to `preferences.json` + // as overrides try? saveThemes() objectWillChange.send() } } + /// The currently selected ``Theme``. @Published public var selectedTheme: Theme? + /// Only themes where ``Theme/appearance`` == ``Theme/ThemeType/dark`` public var darkThemes: [Theme] { themes.filter { $0.appearance == .dark } } + /// Only themes where ``Theme/appearance`` == ``Theme/ThemeType/light`` public var lightThemes: [Theme] { themes.filter { $0.appearance == .light } } - init() { + private init() { do { try loadThemes() self.selectedTheme = themes.first @@ -45,23 +67,35 @@ public class ThemeModel: ObservableObject { } } + /// Loads a theme from a given url and appends it to ``themes``. + /// - Parameter url: The URL of the theme private func load(from url: URL) throws { + // get the data from the provided file let json = try Data(contentsOf: url) + // decode the json into ``Theme`` let theme = try JSONDecoder().decode(Theme.self, from: json) self.themes.append(theme) } + /// Loads all available themes from `~/.codeedit/themes/` + /// + /// If no themes are available, it will create a default theme and save + /// it to the location mentioned above. public func loadThemes() throws { themes.removeAll() let url = baseURL.appendingPathComponent("themes") var isDir: ObjCBool = false + // check if a themes directory exists, otherwise create one if !filemanager.fileExists(atPath: url.path, isDirectory: &isDir) { try filemanager.createDirectory(at: url, withIntermediateDirectories: true) } + // get all filenames in themes folder that end with `.json` let content = try filemanager.contentsOfDirectory(atPath: url.path).filter { $0.contains(".json") } + + // if the folder does not contain any themes create a default bundled theme and return if content.isEmpty { guard let defaultUrl = Bundle.main.url(forResource: "default-dark", withExtension: "json") else { return @@ -69,6 +103,8 @@ public class ThemeModel: ObservableObject { try load(from: defaultUrl) return } + + // load each theme from disk try content.forEach { file in let fileURL = url.appendingPathComponent(file) try load(from: fileURL) @@ -88,8 +124,11 @@ public class ThemeModel: ObservableObject { } } - public let filemanager = FileManager.default - public var baseURL: URL { + /// Default instance of the `FileManager` + private let filemanager = FileManager.default + + /// The base folder url `~/.codeedit/` + private var baseURL: URL { filemanager.homeDirectoryForCurrentUser.appendingPathComponent(".codeedit") } } From dfef953a17f70da6e7b5fc49ee8ca9a0f39ffe50 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Fri, 1 Apr 2022 13:19:40 +0200 Subject: [PATCH 14/26] fix lint errors --- CodeEdit/Info.plist | 2 +- .../AppPreferences/src/ThemePreferences/Model/ThemeModel.swift | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 5c82c3ed81..0883994a72 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 79311f8f93dae32ec3305599311fa20d4fd6615e + ffac43fbefb1a1d0b215e8dc7998053ac00a79cc diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index f77ddb8990..fdb6055cfa 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -24,7 +24,6 @@ public class ThemeModel: ObservableObject { @Published var selectedAppearance: Int = 0 - /// The selected tab in the main section. /// - **0**: Preview /// - **1**: Editor From 490bb0c5b70f89de4cb5b1377a77a963efe542d3 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Fri, 1 Apr 2022 13:52:23 +0200 Subject: [PATCH 15/26] updated file headers --- .../src/{ThemePreferences/Model => Extensions}/Color+HEX.swift | 2 +- .../src/GeneralPreferences/PreferencesGeneralView.swift | 2 +- .../Modules/AppPreferences/src/Model/AppPreferences.swift | 2 +- .../Modules/AppPreferences/src/Model/AppPreferencesModel.swift | 2 +- .../src/TextEditingPreferences/PreferencesTextEditingView.swift | 2 +- .../AppPreferences/src/ThemePreferences/Model/Theme.swift | 2 +- .../AppPreferences/src/ThemePreferences/Model/ThemeModel.swift | 2 +- .../src/ThemePreferences/ThemePreferencesView.swift | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename CodeEditModules/Modules/AppPreferences/src/{ThemePreferences/Model => Extensions}/Color+HEX.swift (98%) diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Color+HEX.swift b/CodeEditModules/Modules/AppPreferences/src/Extensions/Color+HEX.swift similarity index 98% rename from CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Color+HEX.swift rename to CodeEditModules/Modules/AppPreferences/src/Extensions/Color+HEX.swift index bef04a820a..5f3dac5a08 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Color+HEX.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Extensions/Color+HEX.swift @@ -1,5 +1,5 @@ // -// File.swift +// Color+HEX.swift // // // Created by Lukas Pistrol on 23.03.22. diff --git a/CodeEditModules/Modules/AppPreferences/src/GeneralPreferences/PreferencesGeneralView.swift b/CodeEditModules/Modules/AppPreferences/src/GeneralPreferences/PreferencesGeneralView.swift index 576568e93e..06b53cb924 100644 --- a/CodeEditModules/Modules/AppPreferences/src/GeneralPreferences/PreferencesGeneralView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/GeneralPreferences/PreferencesGeneralView.swift @@ -1,5 +1,5 @@ // -// File.swift +// PreferencesGeneralView.swift // // // Created by Lukas Pistrol on 30.03.22. diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift index 65e8f1c872..645fed4c1f 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift @@ -1,5 +1,5 @@ // -// File.swift +// AppPreferences.swift // // // Created by Lukas Pistrol on 01.04.22. diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift index 516bb976fd..d57b5c1543 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferencesModel.swift @@ -1,5 +1,5 @@ // -// File.swift +// AppPreferencesModel.swift // // // Created by Lukas Pistrol on 01.04.22. diff --git a/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift b/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift index d29c0cbff3..71ce6e43cd 100644 --- a/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift @@ -1,5 +1,5 @@ // -// File.swift +// PreferencesTextEditingView.swift // // // Created by Lukas Pistrol on 30.03.22. diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift index f35d1ed1e4..6ac3909d64 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -1,5 +1,5 @@ // -// File.swift +// Theme.swift // // // Created by Lukas Pistrol on 31.03.22. diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index fdb6055cfa..4a4601f0a2 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -1,5 +1,5 @@ // -// File.swift +// ThemeModel.swift // // // Created by Lukas Pistrol on 31.03.22. diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index 72a23c5aea..d9664abda0 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -1,5 +1,5 @@ // -// File.swift +// ThemePreferencesView.swift // // // Created by Lukas Pistrol on 30.03.22. From 3c11c87e93640c16aa97afc0df3d21dc56e40c62 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Fri, 1 Apr 2022 13:53:32 +0200 Subject: [PATCH 16/26] removed commented code --- CodeEdit/AppDelegate.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CodeEdit/AppDelegate.swift b/CodeEdit/AppDelegate.swift index edef6ea027..a9dd6a9492 100644 --- a/CodeEdit/AppDelegate.swift +++ b/CodeEdit/AppDelegate.swift @@ -31,9 +31,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { func applicationDidFinishLaunching(_ notification: Notification) { AppPreferencesModel.shared.preferences.general.appAppearance.applyAppearance() -// if let appearanceString = UserDefaults.standard.string(forKey: Appearances.storageKey) { -// Appearances(rawValue: appearanceString)?.applyAppearance() -// } DispatchQueue.main.async { if NSApp.windows.isEmpty { @@ -78,8 +75,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { func handleOpen() { let behavior = AppPreferencesModel.shared.preferences.general.reopenBehavior -// let behavior = ReopenBehavior(rawValue: UserDefaults.standard.string(forKey: ReopenBehavior.storageKey) -// ?? ReopenBehavior.default.rawValue) ?? ReopenBehavior.default switch behavior { case .welcome: From 5430ab7001c6026513fecad8473e8a15f64d2d97 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 15:16:38 +0200 Subject: [PATCH 17/26] implemented theme overrides, themes can be reset --- CodeEdit/Info.plist | 2 +- .../src/Model/AppPreferences.swift | 15 ++- .../src/ThemePreferences/Model/Theme.swift | 103 ++++++++++++++- .../ThemePreferences/Model/ThemeModel.swift | 121 +++++++++++++++--- .../ThemePreferencesView.swift | 5 + 5 files changed, 225 insertions(+), 21 deletions(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 3e1c42d4c3..1c8b68783b 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 2796d43728c1c91879f0bde9a5b2347e546933dd + 58e3783cf3e1e3945ece4060227dc1a841283f6e diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift index 645fed4c1f..d6766dde29 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift @@ -93,7 +93,7 @@ public extension AppPreferences { public extension AppPreferences { /// A dictionary containing the keys and associated ``Theme/Attributes`` of overridden properties - typealias ThemeOverrides = [String: Theme.Attributes] + typealias ThemeOverrides = [String: [String: Theme.Attributes]] /// The global settings for themes struct ThemePreferences: Codable { @@ -109,9 +109,18 @@ public extension AppPreferences { /// ```json /// "overrides" : { /// "DefaultDark" : { - /// "background" : { - /// "color" : "#123456" + /// "editor" : { + /// "background" : { + /// "color" : "#123456" + /// }, + /// ... /// }, + /// "terminal" : { + /// "blue" : { + /// "color" : "#1100FF" + /// }, + /// ... + /// } /// ... /// }, /// ... diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift index 6ac3909d64..07012159d3 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -93,7 +93,7 @@ public extension Theme { /// /// As of now it only includes the colors `hex` string and /// an accessor for a `SwiftUI` `Color`. - struct Attributes: Codable { + struct Attributes: Codable, Equatable { /// The 24-bit hex string of the color (e.g. #123456) public var color: String @@ -134,6 +134,51 @@ public extension Theme { public var characters: Attributes public var comments: Attributes + subscript(key: String) -> Attributes { + get { + switch key { + case "text": return self.text + case "insertionPoint": return self.insertionPoint + case "invisibles": return self.invisibles + case "background": return self.background + case "lineHighlight": return self.lineHighlight + case "selection": return self.selection + case "keywords": return self.keywords + case "commands": return self.commands + case "types": return self.types + case "attributes": return self.attributes + case "variables": return self.variables + case "values": return self.values + case "numbers": return self.numbers + case "strings": return self.strings + case "characters": return self.characters + case "comments": return self.comments + default: fatalError("Invalid key") + } + } + set { + switch key { + case "text": self.text = newValue + case "insertionPoint": self.insertionPoint = newValue + case "invisibles": self.invisibles = newValue + case "background": self.background = newValue + case "lineHighlight": self.lineHighlight = newValue + case "selection": self.selection = newValue + case "keywords": self.keywords = newValue + case "commands": self.commands = newValue + case "types": self.types = newValue + case "attributes": self.attributes = newValue + case "variables": self.variables = newValue + case "values": self.values = newValue + case "numbers": self.numbers = newValue + case "strings": self.strings = newValue + case "characters": self.characters = newValue + case "comments": self.comments = newValue + default: fatalError("Invalid key") + } + } + } + public init( text: Attributes, insertionPoint: Attributes, @@ -218,6 +263,62 @@ public extension Theme { ] } + // swiftlint:disable function_body_length + subscript(key: String) -> Attributes { + get { + switch key { + case "text": return self.text + case "boldText": return self.boldText + case "cursor": return self.cursor + case "background": return self.background + case "selection": return self.selection + case "black": return self.black + case "red": return self.red + case "green": return self.green + case "yellow": return self.yellow + case "blue": return self.blue + case "magenta": return self.magenta + case "cyan": return self.cyan + case "white": return self.white + case "brightBlack": return self.brightBlack + case "brightRed": return self.brightRed + case "brightGreen": return self.brightGreen + case "brightYellow": return self.brightYellow + case "brightBlue": return self.brightBlue + case "brightMagenta": return self.brightMagenta + case "brightCyan": return self.brightCyan + case "brightWhite": return self.brightWhite + default: fatalError("Invalid key") + } + } + set { + switch key { + case "text": self.text = newValue + case "boldText": self.boldText = newValue + case "cursor": self.cursor = newValue + case "background": self.background = newValue + case "selection": self.selection = newValue + case "black": self.black = newValue + case "red": self.red = newValue + case "green": self.green = newValue + case "yellow": self.yellow = newValue + case "blue": self.blue = newValue + case "magenta": self.magenta = newValue + case "cyan": self.cyan = newValue + case "white": self.white = newValue + case "brightBlack": self.brightBlack = newValue + case "brightRed": self.brightRed = newValue + case "brightGreen": self.brightGreen = newValue + case "brightYellow": self.brightYellow = newValue + case "brightBlue": self.brightBlue = newValue + case "brightMagenta": self.brightMagenta = newValue + case "brightCyan": self.brightCyan = newValue + case "brightWhite": self.brightWhite = newValue + default: fatalError("Invalid key") + } + } + } + init( text: Attributes, boldText: Attributes, diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 4a4601f0a2..0fdcb9f119 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -38,14 +38,18 @@ public class ThemeModel: ObservableObject { // TODO: Don't overwrite themes // Instead save changed values to `preferences.json` // as overrides - try? saveThemes() + saveThemes() objectWillChange.send() } } /// The currently selected ``Theme``. @Published - public var selectedTheme: Theme? + public var selectedTheme: Theme? { + didSet { + AppPreferencesModel.shared.preferences.theme.selectedTheme = selectedTheme?.name + } + } /// Only themes where ``Theme/appearance`` == ``Theme/ThemeType/dark`` public var darkThemes: [Theme] { @@ -60,7 +64,6 @@ public class ThemeModel: ObservableObject { private init() { do { try loadThemes() - self.selectedTheme = themes.first } catch { print(error) } @@ -68,12 +71,12 @@ public class ThemeModel: ObservableObject { /// Loads a theme from a given url and appends it to ``themes``. /// - Parameter url: The URL of the theme - private func load(from url: URL) throws { + private func load(from url: URL) throws -> Theme { // get the data from the provided file let json = try Data(contentsOf: url) // decode the json into ``Theme`` let theme = try JSONDecoder().decode(Theme.self, from: json) - self.themes.append(theme) + return theme } /// Loads all available themes from `~/.codeedit/themes/` @@ -99,27 +102,81 @@ public class ThemeModel: ObservableObject { guard let defaultUrl = Bundle.main.url(forResource: "default-dark", withExtension: "json") else { return } - try load(from: defaultUrl) + self.themes.append(try load(from: defaultUrl)) return } + let prefs = AppPreferencesModel.shared.preferences // load each theme from disk try content.forEach { file in let fileURL = url.appendingPathComponent(file) - try load(from: fileURL) + var theme = try load(from: fileURL) + guard let terminalColors = try theme.terminal.allProperties() as? [String: Theme.Attributes], + let editorColors = try theme.editor.allProperties() as? [String: Theme.Attributes] + else { + print("error") + throw NSError() + } + if let overrides = prefs?.theme.overrides[theme.name]?["terminal"] { + terminalColors.forEach { (key, _) in + if let attributes = overrides[key] { + theme.terminal[key] = attributes + } + } + } + if let overrides = prefs?.theme.overrides[theme.name]?["editor"] { + editorColors.forEach { (key, _) in + if let attributes = overrides[key] { + theme.editor[key] = attributes + } + } + } + self.themes.append(theme) + self.selectedTheme = self.themes.first { $0.name == prefs?.theme.selectedTheme } ?? self.themes.first + print("loaded themes") + } + } + + public func reset(_ theme: Theme) { + AppPreferencesModel.shared.preferences.theme.overrides[theme.name] = [:] + do { + try self.loadThemes() + } catch { + print(error) } } - private func saveThemes() throws { + private func saveThemes() { let url = baseURL.appendingPathComponent("themes") - try themes.forEach { theme in - let data = try JSONEncoder().encode(theme) - let json = try JSONSerialization.jsonObject(with: data) - let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) - try prettyJSON.write( - to: url.appendingPathComponent(theme.name).appendingPathExtension("json"), - options: .atomic - ) + themes.forEach { theme in + do { + let originalUrl = url.appendingPathComponent(theme.name).appendingPathExtension("json") + let originalData = try Data(contentsOf: originalUrl) + let originalTheme = try JSONDecoder().decode(Theme.self, from: originalData) + guard let terminalColors = try theme.terminal.allProperties() as? [String: Theme.Attributes], + let editorColors = try theme.editor.allProperties() as? [String: Theme.Attributes], + let oTermColors = try originalTheme.terminal.allProperties() as? [String: Theme.Attributes], + let oEditColors = try originalTheme.editor.allProperties() as? [String: Theme.Attributes] + else { + throw NSError() + } + var newAttr: [String: [String: Theme.Attributes]] = ["terminal": [:], "editor": [:]] + terminalColors.forEach { (key, value) in + if value != oTermColors[key] { + newAttr["terminal"]?[key] = value + } + } + + editorColors.forEach { (key, value) in + if value != oEditColors[key] { + newAttr["editor"]?[key] = value + } + } + AppPreferencesModel.shared.preferences.theme.overrides[theme.name] = newAttr + + } catch { + print(error) + } } } @@ -131,3 +188,35 @@ public class ThemeModel: ObservableObject { filemanager.homeDirectoryForCurrentUser.appendingPathComponent(".codeedit") } } + +extension Theme: Loopable {} + +extension Theme.EditorColors: Loopable {} +extension Theme.TerminalColors: Loopable {} +extension Theme.Attributes: Loopable {} + +protocol Loopable { + func allProperties() throws -> [String: Any] +} + +extension Loopable { + func allProperties() throws -> [String: Any] { + var result: [String: Any] = [:] + + let mirror = Mirror(reflecting: self) + + guard let style = mirror.displayStyle, style == .struct || style == .class else { + throw NSError() + } + + for (property, value) in mirror.children { + guard let property = property else { + continue + } + + result[property] = value + } + + return result + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index d9664abda0..94abe99e4a 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -73,6 +73,11 @@ public struct ThemePreferencesView: View { colorScheme: themeModel.selectedAppearance == 0 ? .dark : .light ) .transition(.opacity) + .contextMenu { + Button("Reset Theme") { + themeModel.reset(theme) + } + } } } .padding(.vertical, 20) From 6f04c3e9552df9ec649503c0f6d48bd9a648ab93 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 15:52:31 +0200 Subject: [PATCH 18/26] implemented remaining terminal color options --- CodeEdit/AppDelegate.swift | 7 ++ CodeEdit/Info.plist | 2 +- .../PreferencesTerminalView.swift | 57 +++++++++++++++ .../ThemePreferences/TerminalThemeView.swift | 69 +++++++------------ .../ThemePreferencesView.swift | 4 +- 5 files changed, 94 insertions(+), 45 deletions(-) create mode 100644 CodeEditModules/Modules/AppPreferences/src/TerminalPreferences/PreferencesTerminalView.swift diff --git a/CodeEdit/AppDelegate.swift b/CodeEdit/AppDelegate.swift index 7191f53728..9add396af2 100644 --- a/CodeEdit/AppDelegate.swift +++ b/CodeEdit/AppDelegate.swift @@ -180,6 +180,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { ) { PreferencesTextEditingView() }, + Preferences.Pane( + identifier: Preferences.PaneIdentifier("Terminal"), + title: "Terminal", + toolbarIcon: NSImage(systemSymbolName: "terminal", accessibilityDescription: nil)! + ) { + PreferencesTerminalView() + }, Preferences.Pane( identifier: Preferences.PaneIdentifier("KeyBindings"), title: "Key Bindings", diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 1c8b68783b..af75863737 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 58e3783cf3e1e3945ece4060227dc1a841283f6e + 5430ab7001c6026513fecad8473e8a15f64d2d97 diff --git a/CodeEditModules/Modules/AppPreferences/src/TerminalPreferences/PreferencesTerminalView.swift b/CodeEditModules/Modules/AppPreferences/src/TerminalPreferences/PreferencesTerminalView.swift new file mode 100644 index 0000000000..50798f55c6 --- /dev/null +++ b/CodeEditModules/Modules/AppPreferences/src/TerminalPreferences/PreferencesTerminalView.swift @@ -0,0 +1,57 @@ +// +// PreferencesTerminalView.swift +// +// +// Created by Lukas Pistrol on 02.04.22. +// + +import SwiftUI +import Preferences +import FontPicker + +public struct PreferencesTerminalView: View { + + @StateObject + private var prefs: AppPreferencesModel = .shared + + public init() {} + + public var body: some View { + Form { + shellSelector + fontSelector + } + .frame(width: 844) + .padding(30) + } + + private var shellSelector: some View { + Picker("Shell:", selection: $prefs.preferences.terminal.shell) { + Text("System Default") + .tag(AppPreferences.TerminalShell.system) + Divider() + Text("ZSH") + .tag(AppPreferences.TerminalShell.zsh) + Text("Bash") + .tag(AppPreferences.TerminalShell.bash) + } + .fixedSize() + } + + @ViewBuilder + private var fontSelector: some View { + Picker("Font:", selection: $prefs.preferences.terminal.font.customFont) { + Text("System Font") + .tag(false) + Text("Custom") + .tag(true) + } + .fixedSize() + if prefs.preferences.terminal.font.customFont { + FontPicker( + "\(prefs.preferences.terminal.font.name) \(prefs.preferences.terminal.font.size)", + name: $prefs.preferences.terminal.font.name, size: $prefs.preferences.terminal.font.size + ) + } + } +} diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift index f5601b0467..826aeabf81 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift @@ -21,11 +21,9 @@ struct TerminalThemeView: View { ZStack(alignment: .topLeading) { Rectangle() .foregroundColor(Color(NSColor.controlBackgroundColor)) - VStack(alignment: .leading, spacing: 14) { + VStack(alignment: .leading, spacing: 15) { topToggles - shellSelector - fontSelector - Divider() + colorSelector ansiColorSelector } .padding(20) @@ -34,50 +32,35 @@ struct TerminalThemeView: View { private var topToggles: some View { VStack(alignment: .leading) { - Toggle("Use dark terminal appearance", isOn: $prefs.preferences.terminal.darkAppearance) + Toggle("Always use dark terminal appearance", isOn: $prefs.preferences.terminal.darkAppearance) } } - private var shellSelector: some View { - VStack(alignment: .leading, spacing: 5) { - Text("Terminal Shell") + private var colorSelector: some View { + VStack(alignment: .leading, spacing: 0) { + Text("Background & Text") .font(.system(size: 13, weight: .semibold)) .foregroundColor(.secondary) - .padding(.bottom, 5) - Picker("Terminal Shell", selection: $prefs.preferences.terminal.shell) { - Text("System Default") - .tag(AppPreferences.TerminalShell.system) - Divider() - Text("ZSH") - .tag(AppPreferences.TerminalShell.zsh) - Text("Bash") - .tag(AppPreferences.TerminalShell.bash) - } - .fixedSize() - .labelsHidden() - } - } - - private var fontSelector: some View { - VStack(alignment: .leading, spacing: 5) { - Text("Font") - .font(.system(size: 13, weight: .semibold)) - .foregroundColor(.secondary) - .padding(.bottom, 5) - HStack { - Picker("Terminal Font", selection: $prefs.preferences.terminal.font.customFont) { - Text("System Font") - .tag(false) - Text("Custom") - .tag(true) - } - .fixedSize() - .labelsHidden() - if prefs.preferences.terminal.font.customFont { - FontPicker( - "\(prefs.preferences.terminal.font.name) \(prefs.preferences.terminal.font.size)", - name: $prefs.preferences.terminal.font.name, size: $prefs.preferences.terminal.font.size - ) + .padding(.bottom, 10) + HStack(alignment: .top, spacing: 0) { + if let selectedTheme = themeModel.selectedTheme, + let index = themeModel.themes.firstIndex(of: selectedTheme) { + VStack(alignment: .leading, spacing: 10) { + PreferencesColorPicker($themeModel.themes[index].terminal.text.swiftColor, + label: "Text") + PreferencesColorPicker($themeModel.themes[index].terminal.boldText.swiftColor, + label: "Bold Text") + PreferencesColorPicker($themeModel.themes[index].terminal.cursor.swiftColor, + label: "Cursor") + } + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: 10) { + PreferencesColorPicker($themeModel.themes[index].terminal.background.swiftColor, + label: "Background") + PreferencesColorPicker($themeModel.themes[index].terminal.selection.swiftColor, + label: "Selection") + } + .frame(maxWidth: .infinity, alignment: .leading) } } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index 94abe99e4a..feab96dbd7 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -24,7 +24,9 @@ public struct ThemePreferencesView: View { VStack(spacing: 20) { frame HStack(alignment: .center) { - Toggle("Automatically change theme based on system appearance", isOn: .constant(true)) + Toggle("Automatically change theme based on system appearance", isOn: .constant(false)) + .disabled(true) + .help("Not yet implemented") Spacer() Button("Get More Themes...") {} HelpButton {} From 3d788a079427688088ca924330a6c41ddcbc592c Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 16:00:03 +0200 Subject: [PATCH 19/26] themes can now be deleted --- CodeEdit/Info.plist | 2 +- .../src/ThemePreferences/Model/ThemeModel.swift | 13 +++++++++++++ .../src/ThemePreferences/ThemePreferencesView.swift | 9 ++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index af75863737..2d45262900 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 5430ab7001c6026513fecad8473e8a15f64d2d97 + ddd826b0d5ab1ca9a1b7764acc67fd0c13eadf74 diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 0fdcb9f119..a0e074dae2 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -146,6 +146,19 @@ public class ThemeModel: ObservableObject { } } + public func delete(_ theme: Theme) { + let url = baseURL + .appendingPathComponent("themes") + .appendingPathComponent(theme.name) + .appendingPathExtension("json") + do { + try filemanager.removeItem(at: url) + try self.loadThemes() + } catch { + print(error) + } + } + private func saveThemes() { let url = baseURL.appendingPathComponent("themes") themes.forEach { theme in diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index feab96dbd7..a7117d5b99 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -79,6 +79,10 @@ public struct ThemePreferencesView: View { Button("Reset Theme") { themeModel.reset(theme) } + Divider() + Button("Delete Theme", role: .destructive) { + themeModel.delete(theme) + } } } } @@ -99,9 +103,12 @@ public struct ThemePreferencesView: View { Image(systemName: "plus") } .buttonStyle(.plain) - Button {} label: { + Button { + themeModel.delete(themeModel.selectedTheme!) + } label: { Image(systemName: "minus") } + .disabled(themeModel.selectedTheme == nil) .buttonStyle(.plain) Divider() Button { try? themeModel.loadThemes() } label: { From e4acdaae5922d9dbe66e1a84b1d345f261da7cec Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 16:22:12 +0200 Subject: [PATCH 20/26] added list view of themes --- CodeEdit/Info.plist | 2 +- .../ThemePreferences/EditorThemeView.swift | 4 + .../src/ThemePreferences/Model/Theme.swift | 10 +- .../ThemePreferences/PreviewThemeView.swift | 15 ++- .../ThemePreferences/TerminalThemeView.swift | 16 +++- .../ThemePreferencesView.swift | 92 +++++++++++++------ 6 files changed, 95 insertions(+), 44 deletions(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 2d45262900..46f84e96de 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - ddd826b0d5ab1ca9a1b7764acc67fd0c13eadf74 + 3d788a079427688088ca924330a6c41ddcbc592c diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift index 1991150348..69a3c904f3 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/EditorThemeView.swift @@ -79,6 +79,10 @@ struct EditorThemeView: View { } } .padding(20) + } else { + Text("Select a Theme") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) } } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift index 07012159d3..21fcd513e4 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -10,7 +10,7 @@ import SwiftUI /// # Theme /// /// The model structure of themes for the editor & terminal emulator -public struct Theme: Identifiable, Codable, Equatable { +public struct Theme: Identifiable, Codable, Equatable, Hashable { enum CodingKeys: String, CodingKey { case author, license, distributionURL, name, editor, terminal, version @@ -81,7 +81,7 @@ public extension Theme { /// The type of the theme /// - **dark**: this is a theme for dark system appearance /// - **light**: this is a theme for light system appearance - enum ThemeType: String, Codable { + enum ThemeType: String, Codable, Hashable { case dark case light } @@ -93,7 +93,7 @@ public extension Theme { /// /// As of now it only includes the colors `hex` string and /// an accessor for a `SwiftUI` `Color`. - struct Attributes: Codable, Equatable { + struct Attributes: Codable, Equatable, Hashable { /// The 24-bit hex string of the color (e.g. #123456) public var color: String @@ -116,7 +116,7 @@ public extension Theme { public extension Theme { /// The editor colors of the theme - struct EditorColors: Codable { + struct EditorColors: Codable, Hashable { public var text: Attributes public var insertionPoint: Attributes public var invisibles: Attributes @@ -219,7 +219,7 @@ public extension Theme { public extension Theme { /// The terminal emulator colors of the theme - struct TerminalColors: Codable { + struct TerminalColors: Codable, Hashable { public var text: Attributes public var boldText: Attributes public var cursor: Attributes diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/PreviewThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/PreviewThemeView.swift index 9449f7dfc3..d8f7b8a466 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/PreviewThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/PreviewThemeView.swift @@ -8,14 +8,23 @@ import SwiftUI struct PreviewThemeView: View { + + @StateObject + private var themeModel: ThemeModel = .shared + var body: some View { ZStack(alignment: .topLeading) { Rectangle() .foregroundColor(Color(NSColor.controlBackgroundColor)) - VStack(alignment: .leading, spacing: 0) { - Text("Implementation needed") + if themeModel.selectedTheme == nil { + Text("Select a Theme") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } else { + Text("Preview is not yet implemented") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) } - .padding(20) } } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift index 826aeabf81..32e5bb4f32 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/TerminalThemeView.swift @@ -21,12 +21,18 @@ struct TerminalThemeView: View { ZStack(alignment: .topLeading) { Rectangle() .foregroundColor(Color(NSColor.controlBackgroundColor)) - VStack(alignment: .leading, spacing: 15) { - topToggles - colorSelector - ansiColorSelector + if themeModel.selectedTheme == nil { + Text("Select a Theme") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } else { + VStack(alignment: .leading, spacing: 15) { + topToggles + colorSelector + ansiColorSelector + } + .padding(20) } - .padding(20) } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index a7117d5b99..ab903ae953 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -18,6 +18,9 @@ public struct ThemePreferencesView: View { @ObservedObject private var themeModel: ThemeModel = .shared + @State + private var listView: Bool = false + public init() {} public var body: some View { @@ -29,7 +32,11 @@ public struct ThemePreferencesView: View { .help("Not yet implemented") Spacer() Button("Get More Themes...") {} + .disabled(true) + .help("Not yet implemented") HelpButton {} + .disabled(true) + .help("Not yet implemented") } } .frame(width: 872) @@ -60,35 +67,11 @@ public struct ThemePreferencesView: View { ] SegmentedControl($themeModel.selectedAppearance, options: options) } - ScrollView { - let grid: [GridItem] = .init( - repeating: .init(.fixed(130), spacing: 20, alignment: .center), - count: 2 - ) - LazyVGrid(columns: grid, - alignment: .center, - spacing: 20) { - ForEach(themeModel.selectedAppearance == 0 ? themeModel.darkThemes : themeModel.lightThemes) { theme in - ThemePreviewIcon( - theme, - selection: $themeModel.selectedTheme, - colorScheme: themeModel.selectedAppearance == 0 ? .dark : .light - ) - .transition(.opacity) - .contextMenu { - Button("Reset Theme") { - themeModel.reset(theme) - } - Divider() - Button("Delete Theme", role: .destructive) { - themeModel.delete(theme) - } - } - } - } - .padding(.vertical, 20) + if listView { + sidebarLisView + } else { + sidebarScrollView } - .background(Color(NSColor.controlBackgroundColor)) toolbar { sidebarBottomToolbar } @@ -97,6 +80,48 @@ public struct ThemePreferencesView: View { .frame(width: 320) } + private var sidebarLisView: some View { + List(selection: $themeModel.selectedTheme) { + ForEach(themeModel.selectedAppearance == 0 ? themeModel.darkThemes : themeModel.lightThemes) { theme in + Button(theme.name) { themeModel.selectedTheme = theme } + .buttonStyle(.plain) + .tag(theme) + } + } + } + + private var sidebarScrollView: some View { + ScrollView { + let grid: [GridItem] = .init( + repeating: .init(.fixed(130), spacing: 20, alignment: .center), + count: 2 + ) + LazyVGrid(columns: grid, + alignment: .center, + spacing: 20) { + ForEach(themeModel.selectedAppearance == 0 ? themeModel.darkThemes : themeModel.lightThemes) { theme in + ThemePreviewIcon( + theme, + selection: $themeModel.selectedTheme, + colorScheme: themeModel.selectedAppearance == 0 ? .dark : .light + ) + .transition(.opacity) + .contextMenu { + Button("Reset Theme") { + themeModel.reset(theme) + } + Divider() + Button("Delete Theme", role: .destructive) { + themeModel.delete(theme) + } + } + } + } + .padding(.vertical, 20) + } + .background(Color(NSColor.controlBackgroundColor)) + } + private var sidebarBottomToolbar: some View { HStack { Button {} label: { @@ -116,12 +141,19 @@ public struct ThemePreferencesView: View { } .buttonStyle(.plain) Spacer() - Button {} label: { + Button { + listView = true + } label: { Image(systemName: "list.dash") + .foregroundColor(listView ? .accentColor : .primary) } .buttonStyle(.plain) - Button {} label: { + Button { + listView = false + } label: { Image(systemName: "square.grid.2x2") + .symbolVariant(listView ? .none : .fill) + .foregroundColor(listView ? .primary : .accentColor) } .buttonStyle(.plain) } From 837c91f6fc6fac76df02d286bfc6b4175ceda271 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 16:55:35 +0200 Subject: [PATCH 21/26] added documentation --- .../src/Model/AppPreferences.swift | 44 +++++++++++++----- .../src/ThemePreferences/Model/Theme.swift | 24 ++++++++-- .../ThemePreferences/Model/ThemeModel.swift | 46 ++++++++++++++++--- 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift index d6766dde29..384243561d 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift @@ -93,6 +93,23 @@ public extension AppPreferences { public extension AppPreferences { /// A dictionary containing the keys and associated ``Theme/Attributes`` of overridden properties + /// + /// ```json + /// { + /// "editor" : { + /// "background" : { + /// "color" : "#123456" + /// }, + /// ... + /// }, + /// "terminal" : { + /// "blue" : { + /// "color" : "#1100FF" + /// }, + /// ... + /// } + /// } + /// ``` typealias ThemeOverrides = [String: [String: Theme.Attributes]] /// The global settings for themes @@ -107,24 +124,27 @@ public extension AppPreferences { /// Dictionary of themes containing overrides /// /// ```json - /// "overrides" : { - /// "DefaultDark" : { - /// "editor" : { - /// "background" : { - /// "color" : "#123456" + /// { + /// "overrides" : { + /// "DefaultDark" : { + /// "editor" : { + /// "background" : { + /// "color" : "#123456" + /// }, + /// ... /// }, + /// "terminal" : { + /// "blue" : { + /// "color" : "#1100FF" + /// }, + /// ... + /// } /// ... /// }, - /// "terminal" : { - /// "blue" : { - /// "color" : "#1100FF" - /// }, - /// ... - /// } /// ... /// }, /// ... - /// }, + /// } /// ``` public var overrides: [String: ThemeOverrides] = [:] } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift index 21fcd513e4..0e289f789a 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/Theme.swift @@ -10,7 +10,7 @@ import SwiftUI /// # Theme /// /// The model structure of themes for the editor & terminal emulator -public struct Theme: Identifiable, Codable, Equatable, Hashable { +public struct Theme: Identifiable, Codable, Equatable, Hashable, Loopable { enum CodingKeys: String, CodingKey { case author, license, distributionURL, name, editor, terminal, version @@ -93,7 +93,7 @@ public extension Theme { /// /// As of now it only includes the colors `hex` string and /// an accessor for a `SwiftUI` `Color`. - struct Attributes: Codable, Equatable, Hashable { + struct Attributes: Codable, Equatable, Hashable, Loopable { /// The 24-bit hex string of the color (e.g. #123456) public var color: String @@ -116,7 +116,7 @@ public extension Theme { public extension Theme { /// The editor colors of the theme - struct EditorColors: Codable, Hashable { + struct EditorColors: Codable, Hashable, Loopable { public var text: Attributes public var insertionPoint: Attributes public var invisibles: Attributes @@ -134,6 +134,14 @@ public extension Theme { public var characters: Attributes public var comments: Attributes + /// Allows to look up properties by their name + /// + /// **Example:** + /// ```swift + /// editor["text"] + /// // equal to calling + /// editor.text + /// ``` subscript(key: String) -> Attributes { get { switch key { @@ -219,7 +227,7 @@ public extension Theme { public extension Theme { /// The terminal emulator colors of the theme - struct TerminalColors: Codable, Hashable { + struct TerminalColors: Codable, Hashable, Loopable { public var text: Attributes public var boldText: Attributes public var cursor: Attributes @@ -264,6 +272,14 @@ public extension Theme { } // swiftlint:disable function_body_length + /// Allows to look up properties by their name + /// + /// **Example:** + /// ```swift + /// terminal["text"] + /// // equal to calling + /// terminal.text + /// ``` subscript(key: String) -> Attributes { get { switch key { diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index a0e074dae2..108f50ab22 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -71,6 +71,7 @@ public class ThemeModel: ObservableObject { /// Loads a theme from a given url and appends it to ``themes``. /// - Parameter url: The URL of the theme + /// - Returns: A ``Theme`` private func load(from url: URL) throws -> Theme { // get the data from the provided file let json = try Data(contentsOf: url) @@ -83,6 +84,10 @@ public class ThemeModel: ObservableObject { /// /// If no themes are available, it will create a default theme and save /// it to the location mentioned above. + /// + /// When overrides are found in `~/.codeedit/.preferences.json` + /// they are applied to the loaded themes without altering the original + /// the files in `~/.codeedit/themes/`. public func loadThemes() throws { themes.removeAll() let url = baseURL.appendingPathComponent("themes") @@ -137,6 +142,14 @@ public class ThemeModel: ObservableObject { } } + /// Removes all overrides of the given theme in + /// `~/.codeedit/preferences.json` + /// + /// After removing overrides, themes are reloaded + /// from `~/.codeedit/themes`. See ``loadThemes()`` + /// for more information. + /// + /// - Parameter theme: The theme to reset public func reset(_ theme: Theme) { AppPreferencesModel.shared.preferences.theme.overrides[theme.name] = [:] do { @@ -146,6 +159,13 @@ public class ThemeModel: ObservableObject { } } + /// Removes the given theme from `–/.codeedit/themes` + /// + /// After removing the theme, themes are reloaded + /// from `~/.codeedit/themes`. See ``loadThemes()`` + /// for more information. + /// + /// - Parameter theme: The theme to delete public func delete(_ theme: Theme) { let url = baseURL .appendingPathComponent("themes") @@ -159,6 +179,8 @@ public class ThemeModel: ObservableObject { } } + /// Saves changes on theme properties to `overrides` + /// in `~/.codeedit/preferences.json`. private func saveThemes() { let url = baseURL.appendingPathComponent("themes") themes.forEach { theme in @@ -202,17 +224,29 @@ public class ThemeModel: ObservableObject { } } -extension Theme: Loopable {} - -extension Theme.EditorColors: Loopable {} -extension Theme.TerminalColors: Loopable {} -extension Theme.Attributes: Loopable {} - +/// Loopable protocol implements a method that will return all child +/// properties and their associated values of a `Type` protocol Loopable { func allProperties() throws -> [String: Any] } extension Loopable { + + /// returns all child properties and their associated values of `self` + /// + /// **Example:** + /// ```swift + /// struct Author: Loopable { + /// var name: String = "Steve" + /// var books: Int = 4 + /// } + /// + /// let author = Author() + /// print(author.allProperties()) + /// + /// // returns + /// ["name": "Steve", "books": 4] + /// ``` func allProperties() throws -> [String: Any] { var result: [String: Any] = [:] From 4bd318629f17ecb9266c8de6d925ea3dcf05a126 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 17:12:56 +0200 Subject: [PATCH 22/26] added editor font selector (not yet functional) --- CodeEdit/Info.plist | 2 +- .../src/Model/AppPreferences.swift | 14 ++++++++++++++ .../PreferencesTextEditingView.swift | 19 +++++++++++++++++++ .../ThemePreferences/Model/ThemeModel.swift | 5 +---- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 46f84e96de..cac267ab67 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 3d788a079427688088ca924330a6c41ddcbc592c + 837c91f6fc6fac76df02d286bfc6b4175ceda271 diff --git a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift index 384243561d..ffe3935683 100644 --- a/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift +++ b/CodeEditModules/Modules/AppPreferences/src/Model/AppPreferences.swift @@ -193,5 +193,19 @@ public extension AppPreferences { struct TextEditingPreferences: Codable { /// An integer indicating how many spaces a `tab` will generate public var defaultTabWidth: Int = 4 + + /// The font to use in editor. + public var font: EditorFont = .init() + } + + struct EditorFont: Codable { + /// Indicates whether or not to use a custom font + public var customFont: Bool = false + + /// The font size for the custom font + public var size: Int = 11 + + /// The name of the custom font + public var name: String = "SF-MonoMedium" } } diff --git a/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift b/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift index 71ce6e43cd..39ffbb1e65 100644 --- a/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/TextEditingPreferences/PreferencesTextEditingView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import FontPicker public struct PreferencesTextEditingView: View { @@ -16,6 +17,7 @@ public struct PreferencesTextEditingView: View { public var body: some View { Form { + fontSelector HStack { Stepper("Default Tab Width:", value: $prefs.preferences.textEditing.defaultTabWidth, @@ -26,4 +28,21 @@ public struct PreferencesTextEditingView: View { .frame(width: 844) .padding(30) } + + @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 + ) + } + } } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 108f50ab22..849c5c2cb5 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -29,15 +29,12 @@ public class ThemeModel: ObservableObject { /// - **1**: Editor /// - **2**: Terminal @Published - var selectedTab: Int = 2 + var selectedTab: Int = 1 /// An array of loaded ``Theme``. @Published public var themes: [Theme] = [] { didSet { - // TODO: Don't overwrite themes - // Instead save changed values to `preferences.json` - // as overrides saveThemes() objectWillChange.send() } From 2965217ae7de4c3b37fcdd229c59effca61eeae2 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 17:41:13 +0200 Subject: [PATCH 23/26] added context menu to list items, added help descriptions, disabled not implemented buttons --- CodeEdit/Info.plist | 2 +- .../src/ThemePreferences/ThemePreferencesView.swift | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index cac267ab67..6a47734a69 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 837c91f6fc6fac76df02d286bfc6b4175ceda271 + 2baa4946824bc0091e51cb37b823e80a999b187a diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index ab903ae953..bbbdb462bd 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -86,6 +86,15 @@ public struct ThemePreferencesView: View { Button(theme.name) { themeModel.selectedTheme = theme } .buttonStyle(.plain) .tag(theme) + .contextMenu { + Button("Reset Theme") { + themeModel.reset(theme) + } + Divider() + Button("Delete Theme", role: .destructive) { + themeModel.delete(theme) + } + } } } } @@ -127,6 +136,8 @@ public struct ThemePreferencesView: View { Button {} label: { Image(systemName: "plus") } + .disabled(true) + .help("Not yet implemented") .buttonStyle(.plain) Button { themeModel.delete(themeModel.selectedTheme!) @@ -134,6 +145,7 @@ public struct ThemePreferencesView: View { Image(systemName: "minus") } .disabled(themeModel.selectedTheme == nil) + .help("Delete selected theme") .buttonStyle(.plain) Divider() Button { try? themeModel.loadThemes() } label: { From 9785f92a197d9d0e94516af057909aad24eede68 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 18:38:01 +0200 Subject: [PATCH 24/26] fix default theme not appearing in folder, theme cannot be deleted if it is the last one available --- CodeEdit/Info.plist | 2 +- .../src/ThemePreferences/Model/ThemeModel.swift | 8 +++++++- .../src/ThemePreferences/ThemePreferencesView.swift | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 6a47734a69..641dd7e70b 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 2baa4946824bc0091e51cb37b823e80a999b187a + 2965217ae7de4c3b37fcdd229c59effca61eeae2 diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 849c5c2cb5..4175f1921d 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -104,7 +104,13 @@ public class ThemeModel: ObservableObject { guard let defaultUrl = Bundle.main.url(forResource: "default-dark", withExtension: "json") else { return } - self.themes.append(try load(from: defaultUrl)) + let json = try Data(contentsOf: defaultUrl) + let jsonObject = try JSONSerialization.jsonObject(with: json) + let prettyJSON = try JSONSerialization.data(withJSONObject: jsonObject, + options: .prettyPrinted) + + try prettyJSON.write(to: url.appendingPathComponent("Default (Dark).json")) + return } diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index bbbdb462bd..6325b1c0e9 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -94,6 +94,7 @@ public struct ThemePreferencesView: View { Button("Delete Theme", role: .destructive) { themeModel.delete(theme) } + .disabled(themeModel.themes.count <= 1) } } } @@ -123,6 +124,7 @@ public struct ThemePreferencesView: View { Button("Delete Theme", role: .destructive) { themeModel.delete(theme) } + .disabled(themeModel.themes.count <= 1) } } } @@ -144,7 +146,7 @@ public struct ThemePreferencesView: View { } label: { Image(systemName: "minus") } - .disabled(themeModel.selectedTheme == nil) + .disabled(themeModel.selectedTheme == nil || themeModel.themes.count <= 1) .help("Delete selected theme") .buttonStyle(.plain) Divider() From e3a67545e65e3719f05ef880787360c7551564e9 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 18:47:14 +0200 Subject: [PATCH 25/26] removed `swiftlint:disable` --- CodeEdit/Info.plist | 2 +- .../src/ThemePreferences/ThemePreferencesView.swift | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 641dd7e70b..ce075d3b0e 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 2965217ae7de4c3b37fcdd229c59effca61eeae2 + 9785f92a197d9d0e94516af057909aad24eede68 diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift index 6325b1c0e9..2caad2e58e 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/ThemePreferencesView.swift @@ -9,7 +9,6 @@ import FontPicker import SwiftUI import Preferences -// swiftlint:disable all @available(macOS 12, *) public struct ThemePreferencesView: View { @Environment(\.colorScheme) From 4730e5ab17a43d2167f08e70d47a4606c847228d Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Sat, 2 Apr 2022 19:45:31 +0200 Subject: [PATCH 26/26] remove overrides when deleting a theme --- CodeEdit/Info.plist | 2 +- .../AppPreferences/src/ThemePreferences/Model/ThemeModel.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index ce075d3b0e..1302ee1c7b 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -45,6 +45,6 @@ GitHash - 9785f92a197d9d0e94516af057909aad24eede68 + e3a67545e65e3719f05ef880787360c7551564e9 diff --git a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift index 4175f1921d..f9a52126b3 100644 --- a/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift +++ b/CodeEditModules/Modules/AppPreferences/src/ThemePreferences/Model/ThemeModel.swift @@ -176,6 +176,7 @@ public class ThemeModel: ObservableObject { .appendingPathExtension("json") do { try filemanager.removeItem(at: url) + AppPreferencesModel.shared.preferences.theme.overrides.removeValue(forKey: theme.name) try self.loadThemes() } catch { print(error)