diff --git a/ShhShell.xcodeproj/project.pbxproj b/ShhShell.xcodeproj/project.pbxproj index 4fad07b..0afc0d9 100644 --- a/ShhShell.xcodeproj/project.pbxproj +++ b/ShhShell.xcodeproj/project.pbxproj @@ -72,11 +72,12 @@ A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; }; A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A9A5871F2E0BF220006B31E6 /* SwiftTerm */; }; A9BA1D192E1D9AE1005BDCEF /* SwiftTerm.Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */; }; + A9BA1D1B2E1E81CA005BDCEF /* ThemePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BA1D1A2E1E81CA005BDCEF /* ThemePreview.swift */; }; A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; }; A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; }; A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; }; A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */; }; - A9D8192F2E0F1BEE00442D38 /* ThemePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192E2E0F1BEE00442D38 /* ThemePreview.swift */; }; + A9D8192F2E0F1BEE00442D38 /* ThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */; }; A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819302E102D8700442D38 /* HostkeysView.swift */; }; A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97702E0D30ED00142DDC /* HostSymbol.swift */; }; A9DA97732E0D40C100142DDC /* HostSymbolPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97722E0D40C100142DDC /* HostSymbolPreview.swift */; }; @@ -194,11 +195,12 @@ A98554602E058433009051BD /* HostsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsManager.swift; sourceTree = ""; }; A98554622E0587DF009051BD /* HostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsView.swift; sourceTree = ""; }; A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTerm.Color.swift; sourceTree = ""; }; + A9BA1D1A2E1E81CA005BDCEF /* ThemePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreview.swift; sourceTree = ""; }; A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = ""; }; A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = ""; }; A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManagerView.swift; sourceTree = ""; }; - A9D8192E2E0F1BEE00442D38 /* ThemePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreview.swift; sourceTree = ""; }; + A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = ""; }; A9D819302E102D8700442D38 /* HostkeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostkeysView.swift; sourceTree = ""; }; A9DA97702E0D30ED00142DDC /* HostSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbol.swift; sourceTree = ""; }; A9DA97722E0D40C100142DDC /* HostSymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbolPreview.swift; sourceTree = ""; }; @@ -533,9 +535,10 @@ isa = PBXGroup; children = ( A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */, - A9D8192E2E0F1BEE00442D38 /* ThemePreview.swift */, + A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */, A9FD376A2E16DABF005319A8 /* AnsiPickerView.swift */, A9485C772E1BFA5000209824 /* ThemeEditorView.swift */, + A9BA1D1A2E1E81CA005BDCEF /* ThemePreview.swift */, ); path = Themes; sourceTree = ""; @@ -718,11 +721,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A9D8192F2E0F1BEE00442D38 /* ThemePreview.swift in Sources */, + A9D8192F2E0F1BEE00442D38 /* ThemeButton.swift in Sources */, A96C6B022E0C49E800F377FE /* CenteredLabel.swift in Sources */, A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */, A9D819292E0E904200442D38 /* Theme.swift in Sources */, A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */, + A9BA1D1B2E1E81CA005BDCEF /* ThemePreview.swift in Sources */, A9FD375D2E143D7E005319A8 /* KeyStoreError.swift in Sources */, A9835C3C2E17CCA500969508 /* TrafficLights.swift in Sources */, A9485C782E1BFA5000209824 /* ThemeEditorView.swift in Sources */, diff --git a/ShhShell/Views/Themes/ThemeButton.swift b/ShhShell/Views/Themes/ThemeButton.swift new file mode 100644 index 0000000..2167e6e --- /dev/null +++ b/ShhShell/Views/Themes/ThemeButton.swift @@ -0,0 +1,90 @@ +// +// ThemeButton.swift +// ShhShell +// +// Created by neon443 on 27/06/2025. +// + +import SwiftUI + +struct ThemeButton: View { + @ObservedObject var hostsManager: HostsManager + @Binding var theme: Theme + @State var canModify: Bool + + @State private var showRenameAlert: Bool = false + @State private var rename: String = "" + + var isSelected: Bool { + return hostsManager.isThemeSelected(theme) + } + + var body: some View { + let padding: CGFloat = 10 + let innerPadding: CGFloat = 5 + let outerR: CGFloat = 15 + var paletteR: CGFloat { + outerR-padding + } + var selectionR: CGFloat { + outerR-innerPadding + } + + ZStack(alignment: .center) { + Rectangle() + .fill(Color.accentColor) + .opacity(isSelected ? 1 : 0) + Rectangle() + .fill(theme.background.suiColor) + .clipShape( + RoundedRectangle( + cornerRadius: isSelected ? selectionR : 0 + ) + ) + .padding(isSelected ? innerPadding : 0) + + ThemePreview(theme: theme, padding: padding, paletteR: paletteR) + } + .onTapGesture { + hostsManager.selectTheme(theme) + } + .animation(.spring, value: isSelected) + .clipShape(RoundedRectangle(cornerRadius: outerR)) + .contextMenu { + if canModify { + NavigationLink { + ThemeEditorView(hostsManager: hostsManager, theme: $theme) + } label: { + Label("Edit", systemImage: "pencil") + } + Button() { + rename = theme.name + showRenameAlert.toggle() + } label: { + Label("Rename", systemImage: "text.cursor") + } + Button(role: .destructive) { + hostsManager.deleteTheme(theme) + } label: { + Label("Delete", systemImage: "trash") + } + } + } + .alert("Rename \(theme.name)", isPresented: $showRenameAlert) { + TextField("", text: $rename) + Button("OK") { + hostsManager.renameTheme(theme, to: rename) + rename = "" + } + } + } +} + +#Preview { + ThemeButton( + hostsManager: HostsManager(), + theme: .constant(Theme.defaultTheme), + canModify: true + ) + .border(Color.red) +} diff --git a/ShhShell/Views/Themes/ThemeEditorView.swift b/ShhShell/Views/Themes/ThemeEditorView.swift index 32302c8..d84800b 100644 --- a/ShhShell/Views/Themes/ThemeEditorView.swift +++ b/ShhShell/Views/Themes/ThemeEditorView.swift @@ -16,52 +16,55 @@ struct ThemeEditorView: View { @Environment(\.dismiss) var dismiss var body: some View { - NavigationStack { - List { + ZStack { + hostsManager.selectedTheme.background.suiColor.opacity(0.7) + .ignoresSafeArea(.all) + NavigationStack { + ThemePreview(theme: theme, padding: 10, paletteR: 20) + .id(theme) + .padding(.bottom) + .fixedSize(horizontal: false, vertical: true) - Section("Preview") { -// ThemePreview(hostsManager: HostsManager(), theme: .constant(theme), canModify: false) -// .id(theme) - } - - Section("Name") { - TextField("Name", text: $theme.name) - .textFieldStyle(.roundedBorder) - } - - Section("Main Colors") { + List { + Section("Name") { + TextField("Name", text: $theme.name) + .textFieldStyle(.roundedBorder) + } - ColorPicker("Text", selection: $theme.foreground.suiColor, supportsOpacity: false) - ColorPicker("Background", selection: $theme.background.suiColor, supportsOpacity: false) - ColorPicker("Cursor", selection: $theme.cursor.suiColor, supportsOpacity: false) - ColorPicker("Cusor Text", selection: $theme.cursorText.suiColor, supportsOpacity: false) - ColorPicker("Bold Text", selection: $theme.bold.suiColor, supportsOpacity: false) - ColorPicker("Selection", selection: $theme.selection.suiColor, supportsOpacity: false) - ColorPicker("Selected Text", selection: $theme.selectedText.suiColor, supportsOpacity: false) - } - - Section("Ansi Colors") { - ForEach(0...1, id: \.self) { row in - HStack { - Spacer() - ForEach(1...8, id: \.self) { col in - let index = (col + (row * 8)) - 1 - ColorPicker("", selection: $theme.ansi[index].suiColor, supportsOpacity: false) - .labelsHidden() + Section("Main Colors") { + + ColorPicker("Text", selection: $theme.foreground.suiColor, supportsOpacity: false) + ColorPicker("Background", selection: $theme.background.suiColor, supportsOpacity: false) + ColorPicker("Cursor", selection: $theme.cursor.suiColor, supportsOpacity: false) + ColorPicker("Cusor Text", selection: $theme.cursorText.suiColor, supportsOpacity: false) + ColorPicker("Bold Text", selection: $theme.bold.suiColor, supportsOpacity: false) + ColorPicker("Selection", selection: $theme.selection.suiColor, supportsOpacity: false) + ColorPicker("Selected Text", selection: $theme.selectedText.suiColor, supportsOpacity: false) + } + + Section("Ansi Colors") { + ForEach(0...1, id: \.self) { row in + HStack { Spacer() + ForEach(1...8, id: \.self) { col in + let index = (col + (row * 8)) - 1 + ColorPicker("", selection: $theme.ansi[index].suiColor, supportsOpacity: false) + .labelsHidden() + Spacer() + } } } } } - } - .navigationTitle("Edit Theme") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - Button() { - hostsManager.updateTheme(theme) - dismiss() - } label: { - Label("Done", systemImage: "checkmark") + .navigationTitle("Edit Theme") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + Button() { + hostsManager.updateTheme(theme) + dismiss() + } label: { + Label("Done", systemImage: "checkmark") + } } } } diff --git a/ShhShell/Views/Themes/ThemeManagerView.swift b/ShhShell/Views/Themes/ThemeManagerView.swift index e89b203..139479d 100644 --- a/ShhShell/Views/Themes/ThemeManagerView.swift +++ b/ShhShell/Views/Themes/ThemeManagerView.swift @@ -50,6 +50,7 @@ struct ThemeManagerView: View { HStack { Text("Your Themes") .padding(.horizontal) + .padding(.vertical) .font(.headline) Spacer() } @@ -69,7 +70,7 @@ struct ThemeManagerView: View { } else { LazyVGrid(columns: layout, alignment: .center, spacing: 8) { ForEach($hostsManager.themes) { $theme in - ThemePreview(hostsManager: hostsManager, theme: $theme, canModify: true) + ThemeButton(hostsManager: hostsManager, theme: $theme, canModify: true) } } .padding(.horizontal) @@ -85,7 +86,7 @@ struct ThemeManagerView: View { } LazyVGrid(columns: layout, alignment: .center, spacing: 8) { ForEach(Theme.builtinThemes) { theme in - ThemePreview(hostsManager: hostsManager, theme: .constant(theme), canModify: false) + ThemeButton(hostsManager: hostsManager, theme: .constant(theme), canModify: false) } } .padding(.horizontal) diff --git a/ShhShell/Views/Themes/ThemePreview.swift b/ShhShell/Views/Themes/ThemePreview.swift index 46da8f7..3827998 100644 --- a/ShhShell/Views/Themes/ThemePreview.swift +++ b/ShhShell/Views/Themes/ThemePreview.swift @@ -2,115 +2,48 @@ // ThemePreview.swift // ShhShell // -// Created by neon443 on 27/06/2025. +// Created by neon443 on 09/07/2025. // import SwiftUI struct ThemePreview: View { - @ObservedObject var hostsManager: HostsManager - @Binding var theme: Theme - @State var canModify: Bool + @State var theme: Theme + @State var padding: CGFloat + @State var paletteR: CGFloat - @State private var showRenameAlert: Bool = false - @State private var rename: String = "" - - var isSelected: Bool { - return hostsManager.isThemeSelected(theme) - } - - var body: some View { - let padding: CGFloat = 10 - let innerPadding: CGFloat = 5 - let outerR: CGFloat = 15 - var paletteR: CGFloat { - outerR-padding - } - var selectionR: CGFloat { - outerR-innerPadding - } - - ZStack(alignment: .center) { - Rectangle() - .fill(Color.accentColor) - .opacity(isSelected ? 1 : 0) - Rectangle() - .fill(theme.background.suiColor) - .clipShape( - RoundedRectangle( - cornerRadius: isSelected ? selectionR : 0 - ) - ) - .padding(isSelected ? innerPadding : 0) - VStack { - Text(theme.name) - .foregroundStyle(theme.foreground.suiColor) - .font(.headline) - .lineLimit(1) - - Spacer() - - VStack(spacing: 0) { - HStack(spacing: 0) { - ForEach(0..<8, id: \.self) { index in - Rectangle() - .aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit) - .foregroundStyle(theme.ansi[index].suiColor) - } - } - - HStack(spacing: 0) { - ForEach(8..<16, id: \.self) { index in - Rectangle() - .aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit) - .foregroundStyle(theme.ansi[index].suiColor) - } + var body: some View { + VStack { + Text(theme.name) + .foregroundStyle(theme.foreground.suiColor) + .font(.headline) + .lineLimit(1) + + Spacer() + + VStack(spacing: 0) { + HStack(spacing: 0) { + ForEach(0..<8, id: \.self) { index in + Rectangle() + .aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit) + .foregroundStyle(theme.ansi[index].suiColor) } } - .clipShape(RoundedRectangle(cornerRadius: paletteR)) - } - .padding(padding) - } - .onTapGesture { - hostsManager.selectTheme(theme) - } - .animation(.spring, value: isSelected) - .clipShape(RoundedRectangle(cornerRadius: outerR)) - .contextMenu { - if canModify { - NavigationLink { - ThemeEditorView(hostsManager: hostsManager, theme: $theme) - } label: { - Label("Edit", systemImage: "pencil") - } - Button() { - rename = theme.name - showRenameAlert.toggle() - } label: { - Label("Rename", systemImage: "text.cursor") - } - Button(role: .destructive) { - hostsManager.deleteTheme(theme) - } label: { - Label("Delete", systemImage: "trash") + + HStack(spacing: 0) { + ForEach(8..<16, id: \.self) { index in + Rectangle() + .aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit) + .foregroundStyle(theme.ansi[index].suiColor) + } } } + .clipShape(RoundedRectangle(cornerRadius: paletteR)) } - .alert("Rename \(theme.name)", isPresented: $showRenameAlert) { - TextField("", text: $rename) - Button("OK") { - hostsManager.renameTheme(theme, to: rename) - rename = "" - } - } - } + .padding(padding) + } } #Preview { - ThemePreview( - hostsManager: HostsManager(), - theme: .constant(Theme.defaultTheme), - canModify: true - ) - .border(Color.red) + ThemePreview(theme: Theme.defaultTheme, padding: 5, paletteR: 10) }