diff --git a/Resources/themes/0x96f.plist b/Resources/themes/0x96f.plist index dd9de2d..84e3b8d 100644 --- a/Resources/themes/0x96f.plist +++ b/Resources/themes/0x96f.plist @@ -2,6 +2,10 @@ + id + 0x96f + name + 0x96f Ansi 0 Color Alpha Component diff --git a/Resources/themes/catppuccinFrappe.plist b/Resources/themes/catppuccinFrappe.plist index 463d57d..0ec5086 100644 --- a/Resources/themes/catppuccinFrappe.plist +++ b/Resources/themes/catppuccinFrappe.plist @@ -2,6 +2,10 @@ + id + catppuccinFrappe + name + catppuccinFrappe Ansi 0 Color Alpha Component diff --git a/Resources/themes/catppuccinMocha.plist b/Resources/themes/catppuccinMocha.plist index 2947aa3..39d845c 100644 --- a/Resources/themes/catppuccinMocha.plist +++ b/Resources/themes/catppuccinMocha.plist @@ -2,6 +2,10 @@ + id + catppuccinMocha + name + catppuccinMocha Ansi 0 Color Alpha Component diff --git a/Resources/themes/defaultTheme.plist b/Resources/themes/defaultTheme.plist index 98e5f29..7e4275d 100644 --- a/Resources/themes/defaultTheme.plist +++ b/Resources/themes/defaultTheme.plist @@ -2,6 +2,10 @@ + id + defaultTheme + name + defaultTheme Ansi 0 Color Red Component diff --git a/Resources/themes/dracula.plist b/Resources/themes/dracula.plist index 65fbc4a..2ab2649 100644 --- a/Resources/themes/dracula.plist +++ b/Resources/themes/dracula.plist @@ -2,6 +2,10 @@ + id + dracula + name + dracula Ansi 0 Color Alpha Component diff --git a/Resources/themes/gruvboxDark.plist b/Resources/themes/gruvboxDark.plist index 1a18927..1bc4c38 100644 --- a/Resources/themes/gruvboxDark.plist +++ b/Resources/themes/gruvboxDark.plist @@ -2,6 +2,10 @@ + id + gruvboxDark + name + gruvboxDark Ansi 0 Color Alpha Component diff --git a/Resources/themes/iTerm2SolarizedDark.plist b/Resources/themes/iTerm2SolarizedDark.plist index c7e6596..5c709d6 100644 --- a/Resources/themes/iTerm2SolarizedDark.plist +++ b/Resources/themes/iTerm2SolarizedDark.plist @@ -2,6 +2,10 @@ + id + iTerm2SolarizedDark + name + iTerm2SolarizedDark Ansi 0 Color Alpha Component diff --git a/Resources/themes/iTerm2SolarizedLight.plist b/Resources/themes/iTerm2SolarizedLight.plist index f282799..85aad1c 100644 --- a/Resources/themes/iTerm2SolarizedLight.plist +++ b/Resources/themes/iTerm2SolarizedLight.plist @@ -2,6 +2,10 @@ + id + iTerm2SolarizedLight + name + iTerm2SolarizedLight Ansi 0 Color Alpha Component diff --git a/Resources/themes/tomorrowNight.plist b/Resources/themes/tomorrowNight.plist index f98e225..bfcd61c 100644 --- a/Resources/themes/tomorrowNight.plist +++ b/Resources/themes/tomorrowNight.plist @@ -2,6 +2,10 @@ + id + tomorrowNight + name + tomorrowNight Ansi 0 Color Blue Component diff --git a/Resources/themes/ubuntu.plist b/Resources/themes/ubuntu.plist index 04fc0d1..f76e5cb 100644 --- a/Resources/themes/ubuntu.plist +++ b/Resources/themes/ubuntu.plist @@ -2,6 +2,10 @@ + id + ubuntu + name + ubuntu Ansi 0 Color Blue Component diff --git a/Resources/themes/xcodedark.plist b/Resources/themes/xcodedark.plist index 66a59c7..47834bb 100644 --- a/Resources/themes/xcodedark.plist +++ b/Resources/themes/xcodedark.plist @@ -2,6 +2,10 @@ + id + xcodedark + name + xcodedark Ansi 0 Color Alpha Component diff --git a/Resources/themes/xcodedarkhc.plist b/Resources/themes/xcodedarkhc.plist index 31b47b0..9a121ed 100644 --- a/Resources/themes/xcodedarkhc.plist +++ b/Resources/themes/xcodedarkhc.plist @@ -2,6 +2,10 @@ + id + xcodedarkhc + name + xcodedarkhc Ansi 0 Color Alpha Component diff --git a/Resources/themes/xcodewwdc.plist b/Resources/themes/xcodewwdc.plist index 5cff6c7..59ec2e7 100644 --- a/Resources/themes/xcodewwdc.plist +++ b/Resources/themes/xcodewwdc.plist @@ -2,6 +2,10 @@ + id + xcodewwdc + name + xcodewwdc Ansi 0 Color Alpha Component diff --git a/ShhShell.xcodeproj/project.pbxproj b/ShhShell.xcodeproj/project.pbxproj index b3c8b38..886fddb 100644 --- a/ShhShell.xcodeproj/project.pbxproj +++ b/ShhShell.xcodeproj/project.pbxproj @@ -38,6 +38,8 @@ A96BE6A02E10846B00C0FEE9 /* dracula.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE68E2E10846B00C0FEE9 /* dracula.plist */; }; A96BE6A12E10846B00C0FEE9 /* catppuccinFrappe.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE68C2E10846B00C0FEE9 /* catppuccinFrappe.plist */; }; A96BE6A22E10846B00C0FEE9 /* xcodewwdc.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE6962E10846B00C0FEE9 /* xcodewwdc.plist */; }; + A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */; }; + A96BE6A62E113DB000C0FEE9 /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A52E113DB000C0FEE9 /* ColorCodable.swift */; }; A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6A892E0C0B1100F377FE /* SSHState.swift */; }; A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFD2E0C43B600F377FE /* Keypair.swift */; }; A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */; }; @@ -129,6 +131,8 @@ A96BE6942E10846B00C0FEE9 /* xcodedark.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = xcodedark.plist; sourceTree = ""; }; A96BE6952E10846B00C0FEE9 /* xcodedarkhc.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = xcodedarkhc.plist; sourceTree = ""; }; A96BE6962E10846B00C0FEE9 /* xcodewwdc.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = xcodewwdc.plist; sourceTree = ""; }; + A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeCodable.swift; sourceTree = ""; }; + A96BE6A52E113DB000C0FEE9 /* ColorCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCodable.swift; sourceTree = ""; }; A96C6A892E0C0B1100F377FE /* SSHState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHState.swift; sourceTree = ""; }; A96C6AFD2E0C43B600F377FE /* Keypair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keypair.swift; sourceTree = ""; }; A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailView.swift; sourceTree = ""; }; @@ -372,6 +376,8 @@ isa = PBXGroup; children = ( A9D819282E0E904200442D38 /* Theme.swift */, + A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */, + A96BE6A52E113DB000C0FEE9 /* ColorCodable.swift */, ); path = Themes; sourceTree = ""; @@ -550,7 +556,9 @@ A98554632E0587DF009051BD /* HostsView.swift in Sources */, A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */, A9DA97732E0D40C100142DDC /* SymbolPreview.swift in Sources */, + A96BE6A62E113DB000C0FEE9 /* ColorCodable.swift in Sources */, A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */, + A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */, A93143C02DF61B3200FCD5DB /* Host.swift in Sources */, A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */, A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */, diff --git a/ShhShell/Host/HostsManager.swift b/ShhShell/Host/HostsManager.swift index cd67afd..d67be55 100644 --- a/ShhShell/Host/HostsManager.swift +++ b/ShhShell/Host/HostsManager.swift @@ -19,25 +19,22 @@ class HostsManager: ObservableObject, @unchecked Sendable { init() { loadHosts() loadThemes() - print(selectedTheme == Theme.defaultTheme) } func loadThemes() { guard let dataTheme = userDefaults.data(forKey: "themes") else { return } - guard let dataThemeNames = userDefaults.data(forKey: "themeNames") else { return } guard let decodedThemes = try? JSONDecoder().decode([ThemeCodable].self, from: dataTheme) else { return } - guard let decodedThemeNames = try? JSONDecoder().decode([String].self, from: dataThemeNames) else { return } for index in 0.. Int? { diff --git a/ShhShell/Themes/ColorCodable.swift b/ShhShell/Themes/ColorCodable.swift new file mode 100644 index 0000000..d64f8dd --- /dev/null +++ b/ShhShell/Themes/ColorCodable.swift @@ -0,0 +1,57 @@ +// +// ColorCodable.swift +// ShhShell +// +// Created by neon443 on 29/06/2025. +// + +import Foundation +import SwiftTerm +import SwiftUI + +struct ColorCodable: Codable { + var red: Double + var green: Double + var blue: Double + + enum CodingKeys: String, CodingKey { + case red = "Red Component" + case green = "Green Component" + case blue = "Blue Component" + } +} + +extension ColorCodable { + var stColor: SwiftTerm.Color { + return SwiftTerm.Color(self) + } +} + +extension SwiftTerm.Color { + convenience init(_ colorCodable: ColorCodable) { + let red = UInt16(colorCodable.red * 65535) + let green = UInt16(colorCodable.green * 65535) + let blue = UInt16(colorCodable.blue * 65535) + self.init(red: red, green: green, blue: blue) + } + + var colorCodable: ColorCodable { + let red = Double(self.red)/65535 + let green = Double(self.green)/65535 + let blue = Double(self.blue)/65535 + return ColorCodable(red: red, green: green, blue: blue) + } +} + +extension SwiftTerm.Color { + var suiColor: SwiftUI.Color { + return Color(uiColor: self.uiColor) + } + + var uiColor: UIColor { + let red = CGFloat(self.red)/65535 + let green = CGFloat(self.green)/65535 + let blue = CGFloat(self.blue)/65535 + return UIColor(red: red, green: green, blue: blue, alpha: 1) + } +} diff --git a/ShhShell/Themes/Theme.swift b/ShhShell/Themes/Theme.swift index 6b84ecc..9111be1 100644 --- a/ShhShell/Themes/Theme.swift +++ b/ShhShell/Themes/Theme.swift @@ -10,7 +10,7 @@ import SwiftTerm import SwiftUI struct Theme: Hashable, Equatable, Identifiable { - var id: String = UUID().uuidString + var id: String var name: String var ansi: [SwiftTerm.Color] var foreground: SwiftTerm.Color @@ -23,6 +23,7 @@ struct Theme: Hashable, Equatable, Identifiable { var themeCodable: ThemeCodable { return ThemeCodable( + id: id, name: name, ansi0: ansi[0].colorCodable, ansi1: ansi[1].colorCodable, @@ -50,7 +51,7 @@ struct Theme: Hashable, Equatable, Identifiable { ) } - static func decodeTheme(name: String, data: Data?) -> Theme? { + static func decodeTheme( data: Data?) -> Theme? { guard let data else { fatalError() } let plistDecoder = PropertyListDecoder() @@ -60,18 +61,7 @@ struct Theme: Hashable, Equatable, Identifiable { (try? plistDecoder.decode(ThemeCodable.self, from: data)) ?? (try? jsonDecoder.decode(ThemeCodable.self, from: data)) else { fatalError() } - var theme = Theme( - name: decoded.name ?? name, - ansi: decoded.ansi, - foreground: Color(decoded.foreground), - background: Color(decoded.background), - cursor: Color(decoded.cursor), - cursorText: Color(decoded.cursorText), - bold: Color(decoded.bold), - selectedText: Color(decoded.selectedText), - selection: Color(decoded.selection) - ) - return theme + return decoded.toTheme() } static func decodeLocalTheme(fileName: String) -> Theme? { @@ -80,10 +70,7 @@ struct Theme: Hashable, Equatable, Identifiable { guard let fileContents = try? Data(contentsOf: path) else { return nil } - guard var theme = Theme.decodeTheme(name: themeName, data: fileContents) else { return nil } - theme.name = themeName - theme.id = themeName - return theme + return Theme.decodeTheme(data: fileContents) } static var defaultTheme: Theme { @@ -93,6 +80,17 @@ struct Theme: Hashable, Equatable, Identifiable { static var builtinThemes: [Theme] { return ThemesBuiltin.allCases.map({ decodeLocalTheme(fileName: $0.rawValue)! }) } + +// static func ==(lhs: Theme, rhs: Theme) -> Bool { +// return lhs.ansi == rhs.ansi && +// lhs.foreground == rhs.foreground && +// lhs.background == rhs.background && +// lhs.cursor == rhs.cursor && +// lhs.cursorText == rhs.cursorText && +// lhs.bold == rhs.bold && +// lhs.selectedText == rhs.selectedText && +// lhs.selection == rhs.selection +// } } enum ThemesBuiltin: String, CaseIterable, Hashable, Equatable { @@ -110,105 +108,3 @@ enum ThemesBuiltin: String, CaseIterable, Hashable, Equatable { case gruvboxDark = "gruvboxDark" case ubuntu = "ubuntu" } - -struct ThemeCodable: Codable { - var name: String? - var ansi0: ColorCodable - var ansi1: ColorCodable - var ansi2: ColorCodable - var ansi3: ColorCodable - var ansi4: ColorCodable - var ansi5: ColorCodable - var ansi6: ColorCodable - var ansi7: ColorCodable - var ansi8: ColorCodable - var ansi9: ColorCodable - var ansi10: ColorCodable - var ansi11: ColorCodable - var ansi12: ColorCodable - var ansi13: ColorCodable - var ansi14: ColorCodable - var ansi15: ColorCodable - var foreground: ColorCodable - var background: ColorCodable - var cursor: ColorCodable - var cursorText: ColorCodable - var bold: ColorCodable - var selectedText: ColorCodable - var selection: ColorCodable - - enum CodingKeys: String, CodingKey { - case ansi0 = "Ansi 0 Color" - case ansi1 = "Ansi 1 Color" - case ansi2 = "Ansi 2 Color" - case ansi3 = "Ansi 3 Color" - case ansi4 = "Ansi 4 Color" - case ansi5 = "Ansi 5 Color" - case ansi6 = "Ansi 6 Color" - case ansi7 = "Ansi 7 Color" - case ansi8 = "Ansi 8 Color" - case ansi9 = "Ansi 9 Color" - case ansi10 = "Ansi 10 Color" - case ansi11 = "Ansi 11 Color" - case ansi12 = "Ansi 12 Color" - case ansi13 = "Ansi 13 Color" - case ansi14 = "Ansi 14 Color" - case ansi15 = "Ansi 15 Color" - case foreground = "Foreground Color" - case background = "Background Color" - case cursor = "Cursor Color" - case cursorText = "Cursor Text Color" - case bold = "Bold Color" - case selectedText = "Selected Text Color" - case selection = "Selection Color" - } -} - -extension ThemeCodable { - var ansi: [SwiftTerm.Color] { - let arr = [ansi0, ansi1, ansi2, ansi3, ansi4, ansi5, ansi6, ansi7, ansi8, ansi9, ansi10, ansi11, ansi12, ansi13, ansi14, ansi15] - return arr.map(SwiftTerm.Color.init) - } -} - -struct ColorCodable: Codable { - var red: Double - var green: Double - var blue: Double - - enum CodingKeys: String, CodingKey { - case red = "Red Component" - case green = "Green Component" - case blue = "Blue Component" - } -} - - -extension SwiftTerm.Color { - convenience init(_ colorCodable: ColorCodable) { - let red = UInt16(colorCodable.red * 65535) - let green = UInt16(colorCodable.green * 65535) - let blue = UInt16(colorCodable.blue * 65535) - self.init(red: red, green: green, blue: blue) - } - - var colorCodable: ColorCodable { - let red = Double(self.red)/65535 - let green = Double(self.green)/65535 - let blue = Double(self.blue)/65535 - return ColorCodable(red: red, green: green, blue: blue) - } -} - -extension SwiftTerm.Color { - var suiColor: SwiftUI.Color { - return Color(uiColor: self.uiColor) - } - - var uiColor: UIColor { - let red = CGFloat(self.red)/65535 - let green = CGFloat(self.green)/65535 - let blue = CGFloat(self.blue)/65535 - return UIColor(red: red, green: green, blue: blue, alpha: 1) - } -} diff --git a/ShhShell/Themes/ThemeCodable.swift b/ShhShell/Themes/ThemeCodable.swift new file mode 100644 index 0000000..7e61065 --- /dev/null +++ b/ShhShell/Themes/ThemeCodable.swift @@ -0,0 +1,89 @@ +// +// ThemeCodable.swift +// ShhShell +// +// Created by neon443 on 29/06/2025. +// + +import Foundation +import SwiftTerm + +struct ThemeCodable: Codable { + var id: String? + var name: String? + var ansi0: ColorCodable + var ansi1: ColorCodable + var ansi2: ColorCodable + var ansi3: ColorCodable + var ansi4: ColorCodable + var ansi5: ColorCodable + var ansi6: ColorCodable + var ansi7: ColorCodable + var ansi8: ColorCodable + var ansi9: ColorCodable + var ansi10: ColorCodable + var ansi11: ColorCodable + var ansi12: ColorCodable + var ansi13: ColorCodable + var ansi14: ColorCodable + var ansi15: ColorCodable + var foreground: ColorCodable + var background: ColorCodable + var cursor: ColorCodable + var cursorText: ColorCodable + var bold: ColorCodable + var selectedText: ColorCodable + var selection: ColorCodable + + enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case ansi0 = "Ansi 0 Color" + case ansi1 = "Ansi 1 Color" + case ansi2 = "Ansi 2 Color" + case ansi3 = "Ansi 3 Color" + case ansi4 = "Ansi 4 Color" + case ansi5 = "Ansi 5 Color" + case ansi6 = "Ansi 6 Color" + case ansi7 = "Ansi 7 Color" + case ansi8 = "Ansi 8 Color" + case ansi9 = "Ansi 9 Color" + case ansi10 = "Ansi 10 Color" + case ansi11 = "Ansi 11 Color" + case ansi12 = "Ansi 12 Color" + case ansi13 = "Ansi 13 Color" + case ansi14 = "Ansi 14 Color" + case ansi15 = "Ansi 15 Color" + case foreground = "Foreground Color" + case background = "Background Color" + case cursor = "Cursor Color" + case cursorText = "Cursor Text Color" + case bold = "Bold Color" + case selectedText = "Selected Text Color" + case selection = "Selection Color" + } +} + +extension ThemeCodable { + var ansi: [SwiftTerm.Color] { + let arr = [ansi0, ansi1, ansi2, ansi3, ansi4, ansi5, ansi6, ansi7, ansi8, ansi9, ansi10, ansi11, ansi12, ansi13, ansi14, ansi15] + return arr.map(SwiftTerm.Color.init) + } +} + +extension ThemeCodable { + func toTheme() -> Theme { + return Theme( + id: id ?? UUID().uuidString, + name: self.name ?? "", + ansi: self.ansi, + foreground: self.foreground.stColor, + background: self.background.stColor, + cursor: self.cursor.stColor, + cursorText: self.cursorText.stColor, + bold: self.bold.stColor, + selectedText: self.selectedText.stColor, + selection: self.selection.stColor + ) + } +} diff --git a/ShhShell/Views/Themes/ThemeManagerView.swift b/ShhShell/Views/Themes/ThemeManagerView.swift index 1d46e9f..f357725 100644 --- a/ShhShell/Views/Themes/ThemeManagerView.swift +++ b/ShhShell/Views/Themes/ThemeManagerView.swift @@ -83,6 +83,7 @@ struct ThemeManagerView: View { ThemePreview(hostsManager: hostsManager, theme: theme) } } + .animation(.default, value: hostsManager.themes) } .scrollIndicators(.hidden) .fixedSize(horizontal: false, vertical: true) diff --git a/ShhShell/Views/Themes/ThemePreview.swift b/ShhShell/Views/Themes/ThemePreview.swift index f0f4d68..21fa04a 100644 --- a/ShhShell/Views/Themes/ThemePreview.swift +++ b/ShhShell/Views/Themes/ThemePreview.swift @@ -63,6 +63,6 @@ struct ThemePreview: View { ThemePreview( hostsManager: HostsManager(), - theme: Theme.decodeTheme(name: "theme", data: data)! + theme: Theme.decodeTheme(data: data)! ) }