From 19d3c495d50e0cbf51da1f10c18fe3819aef64d1 Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Tue, 8 Jul 2025 19:48:37 +0100 Subject: [PATCH] added reloading for edited themes on the fly added a get/set SwiftUI.Color to SwiftTerm.Color overhaul themeeditor's state stuff updated themeeditor to use a binding updated themepreview to use a binding remove the massive subscript - its not needed anymore moved all swifterm color stuff to its own file --- ShhShell.xcodeproj/project.pbxproj | 4 + ShhShell/Host/HostsManager.swift | 8 +- ShhShell/Themes/ColorCodable.swift | 50 --------- ShhShell/Themes/SwiftTerm.Color.swift | 77 +++++++++++++ ShhShell/Themes/Theme.swift | 107 +++++++++++++++++-- ShhShell/Themes/ThemeCodable.swift | 96 ----------------- ShhShell/Views/Themes/ThemeEditorView.swift | 83 +++++++------- ShhShell/Views/Themes/ThemeManagerView.swift | 10 +- ShhShell/Views/Themes/ThemePreview.swift | 9 +- 9 files changed, 230 insertions(+), 214 deletions(-) create mode 100644 ShhShell/Themes/SwiftTerm.Color.swift diff --git a/ShhShell.xcodeproj/project.pbxproj b/ShhShell.xcodeproj/project.pbxproj index 2fb5549..4fad07b 100644 --- a/ShhShell.xcodeproj/project.pbxproj +++ b/ShhShell.xcodeproj/project.pbxproj @@ -71,6 +71,7 @@ A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; }; 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 */; }; 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 */; }; @@ -192,6 +193,7 @@ A985545C2E055D4D009051BD /* ConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionView.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -522,6 +524,7 @@ A9D819282E0E904200442D38 /* Theme.swift */, A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */, A96BE6A52E113DB000C0FEE9 /* ColorCodable.swift */, + A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */, ); path = Themes; sourceTree = ""; @@ -735,6 +738,7 @@ A9FD375F2E14648E005319A8 /* KeyImporterView.swift in Sources */, A93143C02DF61B3200FCD5DB /* Host.swift in Sources */, A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */, + A9BA1D192E1D9AE1005BDCEF /* SwiftTerm.Color.swift in Sources */, A96BE6AD2E11825800C0FEE9 /* SessionView.swift in Sources */, A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */, A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */, diff --git a/ShhShell/Host/HostsManager.swift b/ShhShell/Host/HostsManager.swift index 47e6c0c..048d9e2 100644 --- a/ShhShell/Host/HostsManager.swift +++ b/ShhShell/Host/HostsManager.swift @@ -61,19 +61,24 @@ class HostsManager: ObservableObject, @unchecked Sendable { guard let decodedThemes = try? JSONDecoder().decode([ThemeCodable].self, from: dataTheme) else { return } + print(themes.count) self.themes = [] + print(themes.count) + objectWillChange.send() for index in 0.. 1 { cc.red = 1 } - if cc.green > 1 { cc.green = 1 } - if cc.blue > 1 { cc.blue = 1 } - - let red = UInt16(cc.red * 65535) - let green = UInt16(cc.green * 65535) - let blue = UInt16(cc.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) - } - - 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) - } - - var luminance: Double { - let r = Double(red)/65535 - let g = Double(green)/65535 - let b = Double(blue)/65535 - return (0.2126*r + 0.7152*g + 0.0722*b) - } -} diff --git a/ShhShell/Themes/SwiftTerm.Color.swift b/ShhShell/Themes/SwiftTerm.Color.swift new file mode 100644 index 0000000..2d66790 --- /dev/null +++ b/ShhShell/Themes/SwiftTerm.Color.swift @@ -0,0 +1,77 @@ +// +// SwiftTerm.Color.swift +// ShhShell +// +// Created by neon443 on 08/07/2025. +// + +import Foundation +import SwiftUI +import SwiftTerm + +extension SwiftTerm.Color { + var suiColor: SwiftUI.Color { + get { + return Color(uiColor: self.uiColor) + } set { + let newOne = SwiftTerm.Color(newValue) + self.red = newOne.red + self.green = newOne.green + self.blue = newOne.blue +// let uiColor = UIColor(newValue) +// var r: CGFloat = 0; var g: CGFloat = 0; var b: CGFloat = 0 +// uiColor.getRed(&r, green: &g, blue: &b, alpha: nil) +// self.red = r*65535 + } + } +} + +extension SwiftTerm.Color { + 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) + } + + 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) + } +} + +extension SwiftTerm.Color { + convenience init(_ color: SwiftUI.Color) { + var r: CGFloat = 0; var g: CGFloat = 0; var b: CGFloat = 0; var a: CGFloat = 0 + let uiColor = UIColor(color) + uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) + + self.init(red: UInt16(r*65535), green: UInt16(g*65535), blue: UInt16(b*65535)) + } + + convenience init(_ colorCodable: ColorCodable) { + var cc = colorCodable + if cc.red < 0 { cc.red.negate() } + if cc.green < 0 { cc.green.negate() } + if cc.blue < 0 { cc.blue.negate() } + if cc.red > 1 { cc.red = 1 } + if cc.green > 1 { cc.green = 1 } + if cc.blue > 1 { cc.blue = 1 } + + let red = UInt16(cc.red * 65535) + let green = UInt16(cc.green * 65535) + let blue = UInt16(cc.blue * 65535) + self.init(red: red, green: green, blue: blue) + } +} + +extension SwiftTerm.Color { + var luminance: Double { + let r = Double(red)/65535 + let g = Double(green)/65535 + let b = Double(blue)/65535 + return (0.2126*r + 0.7152*g + 0.0722*b) + } +} diff --git a/ShhShell/Themes/Theme.swift b/ShhShell/Themes/Theme.swift index acb089f..dacc425 100644 --- a/ShhShell/Themes/Theme.swift +++ b/ShhShell/Themes/Theme.swift @@ -77,17 +77,6 @@ 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 { @@ -105,3 +94,99 @@ enum ThemesBuiltin: String, CaseIterable, Hashable, Equatable { case gruvboxDark = "gruvboxDark" case ubuntu = "ubuntu" } + +//extension ThemeCodable { +// subscript(ansiIndex index: Int) -> SwiftUI.Color { +// get { +// switch index { +// case 0: return ansi0.stColor.suiColor +// case 1: return ansi1.stColor.suiColor +// case 2: return ansi2.stColor.suiColor +// case 3: return ansi3.stColor.suiColor +// case 4: return ansi4.stColor.suiColor +// case 5: return ansi5.stColor.suiColor +// case 6: return ansi6.stColor.suiColor +// case 7: return ansi7.stColor.suiColor +// case 8: return ansi8.stColor.suiColor +// case 9: return ansi9.stColor.suiColor +// case 10: return ansi10.stColor.suiColor +// case 11: return ansi11.stColor.suiColor +// case 12: return ansi12.stColor.suiColor +// case 13: return ansi13.stColor.suiColor +// case 14: return ansi14.stColor.suiColor +// case 15: return ansi15.stColor.suiColor +// default: fatalError() +// } +// } +// set { +// let cc = ColorCodable(color: newValue) +// switch index { +// case 0: +// ansi0.red = cc.red +// ansi0.green = cc.green +// ansi0.blue = cc.blue +// case 1: +// ansi1.red = cc.red +// ansi1.green = cc.green +// ansi1.blue = cc.blue +// case 2: +// ansi2.red = cc.red +// ansi2.green = cc.green +// ansi2.blue = cc.blue +// case 3: +// ansi3.red = cc.red +// ansi3.green = cc.green +// ansi3.blue = cc.blue +// case 4: +// ansi4.red = cc.red +// ansi4.green = cc.green +// ansi4.blue = cc.blue +// case 5: +// ansi5.red = cc.red +// ansi5.green = cc.green +// ansi5.blue = cc.blue +// case 6: +// ansi6.red = cc.red +// ansi6.green = cc.green +// ansi6.blue = cc.blue +// case 7: +// ansi7.red = cc.red +// ansi7.green = cc.green +// ansi7.blue = cc.blue +// case 8: +// ansi8.red = cc.red +// ansi8.green = cc.green +// ansi8.blue = cc.blue +// case 9: +// ansi9.red = cc.red +// ansi9.green = cc.green +// ansi9.blue = cc.blue +// case 10: +// ansi10.red = cc.red +// ansi10.green = cc.green +// ansi10.blue = cc.blue +// case 11: +// ansi11.red = cc.red +// ansi11.green = cc.green +// ansi11.blue = cc.blue +// case 12: +// ansi12.red = cc.red +// ansi12.green = cc.green +// ansi12.blue = cc.blue +// case 13: +// ansi13.red = cc.red +// ansi13.green = cc.green +// ansi13.blue = cc.blue +// case 14: +// ansi14.red = cc.red +// ansi14.green = cc.green +// ansi14.blue = cc.blue +// case 15: +// ansi15.red = cc.red +// ansi15.green = cc.green +// ansi15.blue = cc.blue +// default: fatalError() +// } +// } +// } +//} diff --git a/ShhShell/Themes/ThemeCodable.swift b/ShhShell/Themes/ThemeCodable.swift index e286499..d753490 100644 --- a/ShhShell/Themes/ThemeCodable.swift +++ b/ShhShell/Themes/ThemeCodable.swift @@ -88,99 +88,3 @@ extension ThemeCodable { ) } } - -extension ThemeCodable { - subscript(ansiIndex index: Int) -> SwiftUI.Color { - get { - switch index { - case 0: return ansi0.stColor.suiColor - case 1: return ansi1.stColor.suiColor - case 2: return ansi2.stColor.suiColor - case 3: return ansi3.stColor.suiColor - case 4: return ansi4.stColor.suiColor - case 5: return ansi5.stColor.suiColor - case 6: return ansi6.stColor.suiColor - case 7: return ansi7.stColor.suiColor - case 8: return ansi8.stColor.suiColor - case 9: return ansi9.stColor.suiColor - case 10: return ansi10.stColor.suiColor - case 11: return ansi11.stColor.suiColor - case 12: return ansi12.stColor.suiColor - case 13: return ansi13.stColor.suiColor - case 14: return ansi14.stColor.suiColor - case 15: return ansi15.stColor.suiColor - default: fatalError() - } - } - set { - let cc = ColorCodable(color: newValue) - switch index { - case 0: - ansi0.red = cc.red - ansi0.green = cc.green - ansi0.blue = cc.blue - case 1: - ansi1.red = cc.red - ansi1.green = cc.green - ansi1.blue = cc.blue - case 2: - ansi2.red = cc.red - ansi2.green = cc.green - ansi2.blue = cc.blue - case 3: - ansi3.red = cc.red - ansi3.green = cc.green - ansi3.blue = cc.blue - case 4: - ansi4.red = cc.red - ansi4.green = cc.green - ansi4.blue = cc.blue - case 5: - ansi5.red = cc.red - ansi5.green = cc.green - ansi5.blue = cc.blue - case 6: - ansi6.red = cc.red - ansi6.green = cc.green - ansi6.blue = cc.blue - case 7: - ansi7.red = cc.red - ansi7.green = cc.green - ansi7.blue = cc.blue - case 8: - ansi8.red = cc.red - ansi8.green = cc.green - ansi8.blue = cc.blue - case 9: - ansi9.red = cc.red - ansi9.green = cc.green - ansi9.blue = cc.blue - case 10: - ansi10.red = cc.red - ansi10.green = cc.green - ansi10.blue = cc.blue - case 11: - ansi11.red = cc.red - ansi11.green = cc.green - ansi11.blue = cc.blue - case 12: - ansi12.red = cc.red - ansi12.green = cc.green - ansi12.blue = cc.blue - case 13: - ansi13.red = cc.red - ansi13.green = cc.green - ansi13.blue = cc.blue - case 14: - ansi14.red = cc.red - ansi14.green = cc.green - ansi14.blue = cc.blue - case 15: - ansi15.red = cc.red - ansi15.green = cc.green - ansi15.blue = cc.blue - default: fatalError() - } - } - } -} diff --git a/ShhShell/Views/Themes/ThemeEditorView.swift b/ShhShell/Views/Themes/ThemeEditorView.swift index da3d17e..53b10b6 100644 --- a/ShhShell/Views/Themes/ThemeEditorView.swift +++ b/ShhShell/Views/Themes/ThemeEditorView.swift @@ -11,63 +11,60 @@ import SwiftTerm struct ThemeEditorView: View { @ObservedObject var hostsManager: HostsManager - // @State var theme: Theme - @State var themeCodable: ThemeCodable + @Binding var theme: Theme - init(hostsManager: HostsManager, theme: Theme) { - self.hostsManager = hostsManager - // self.theme = theme - self.themeCodable = theme.themeCodable - } + @Environment(\.dismiss) var dismiss var body: some View { NavigationStack { List { - TextField( - "Name", - text: Binding(get: { themeCodable.name ?? "Theme" }, set: { themeCodable.name = $0 }) - ) - .textFieldStyle(.roundedBorder) - ThemePreview(hostsManager: HostsManager(), theme: themeCodable.toTheme(), canModify: false) - .id(themeCodable) - - Group { - HStack { - Rectangle() - .fill(themeCodable.foreground.suiColor) - Text("Foreground") - Spacer() - ColorPicker("", selection: $themeCodable.foreground.suiColor, supportsOpacity: false) - .labelsHidden() - } - Rectangle() - .fill(themeCodable.background.suiColor) - Rectangle() - .fill(themeCodable.bold.suiColor) - Rectangle() - .fill(themeCodable.cursor.suiColor) - Rectangle() - .fill(themeCodable.cursorText.suiColor) - Rectangle() - .fill(themeCodable.selection.suiColor) - Rectangle() - .fill(themeCodable.selectedText.suiColor) + Section("Preview") { +// ThemePreview(hostsManager: HostsManager(), theme: themeCodable.toTheme(), canModify: false) +// .id(themeCodable) } - ForEach(0...1, id: \.self) { row in - HStack { - ForEach(1...8, id: \.self) { col in - let index = (col + (row * 8)) - 1 - ColorPicker("Ansi \(index+1)", selection: $themeCodable[ansiIndex: index], supportsOpacity: false) + Section("Name") { + TextField("Name", text: $theme.name) + .textFieldStyle(.roundedBorder) + } + + Section("Main Colors") { + + ColorPicker("Text", selection: $theme.foreground.suiColor, supportsOpacity: false) + .labelsHidden() + ColorPicker("Background", selection: $theme.background.suiColor, supportsOpacity: false) + .labelsHidden() + ColorPicker("Cursor", selection: $theme.cursor.suiColor, supportsOpacity: false) + .labelsHidden() + ColorPicker("Cusor Text", selection: $theme.cursorText.suiColor, supportsOpacity: false) + .labelsHidden() + ColorPicker("Bold Text", selection: $theme.bold.suiColor, supportsOpacity: false) + .labelsHidden() + ColorPicker("Selection", selection: $theme.selection.suiColor, supportsOpacity: false) + .labelsHidden() + ColorPicker("Selected Text", selection: $theme.selectedText.suiColor, supportsOpacity: false) + .labelsHidden() + } + + Section("Ansi Colors") { + ForEach(0...1, id: \.self) { row in + HStack { + ForEach(1...8, id: \.self) { col in + let index = (col + (row * 8)) - 1 + ColorPicker("", selection: $theme.ansi[index].suiColor, supportsOpacity: false) + .labelsHidden() + } } } } } .navigationTitle("Edit Theme") + .navigationBarTitleDisplayMode(.inline) .toolbar { Button() { - hostsManager.updateTheme(themeCodable.toTheme()) + hostsManager.updateTheme(theme) + dismiss() } label: { Label("Done", systemImage: "checkmark") } @@ -77,5 +74,5 @@ struct ThemeEditorView: View { } #Preview { - ThemeEditorView(hostsManager: HostsManager(), theme: Theme.defaultTheme) + ThemeEditorView(hostsManager: HostsManager(), theme: .constant(Theme.defaultTheme)) } diff --git a/ShhShell/Views/Themes/ThemeManagerView.swift b/ShhShell/Views/Themes/ThemeManagerView.swift index c260e62..e89b203 100644 --- a/ShhShell/Views/Themes/ThemeManagerView.swift +++ b/ShhShell/Views/Themes/ThemeManagerView.swift @@ -14,10 +14,6 @@ struct ThemeManagerView: View { @State var importURL: String = "" @State var toImportName: String = "" - @State var showRenameAlert: Bool = false - @State var themeToRename: Theme? - @State var rename: String = "" - var minColWidth: CGFloat {150} var spacing: CGFloat {8} var grid: GridItem { @@ -72,8 +68,8 @@ struct ThemeManagerView: View { } } else { LazyVGrid(columns: layout, alignment: .center, spacing: 8) { - ForEach(hostsManager.themes) { theme in - ThemePreview(hostsManager: hostsManager, theme: theme, canModify: true) + ForEach($hostsManager.themes) { $theme in + ThemePreview(hostsManager: hostsManager, theme: $theme, canModify: true) } } .padding(.horizontal) @@ -89,7 +85,7 @@ struct ThemeManagerView: View { } LazyVGrid(columns: layout, alignment: .center, spacing: 8) { ForEach(Theme.builtinThemes) { theme in - ThemePreview(hostsManager: hostsManager, theme: theme, canModify: false) + ThemePreview(hostsManager: hostsManager, theme: .constant(theme), canModify: false) } } .padding(.horizontal) diff --git a/ShhShell/Views/Themes/ThemePreview.swift b/ShhShell/Views/Themes/ThemePreview.swift index 04d5f2b..46da8f7 100644 --- a/ShhShell/Views/Themes/ThemePreview.swift +++ b/ShhShell/Views/Themes/ThemePreview.swift @@ -9,7 +9,7 @@ import SwiftUI struct ThemePreview: View { @ObservedObject var hostsManager: HostsManager - @State var theme: Theme + @Binding var theme: Theme @State var canModify: Bool @State private var showRenameAlert: Bool = false @@ -79,7 +79,7 @@ struct ThemePreview: View { .contextMenu { if canModify { NavigationLink { - ThemeEditorView(hostsManager: hostsManager, theme: theme) + ThemeEditorView(hostsManager: hostsManager, theme: $theme) } label: { Label("Edit", systemImage: "pencil") } @@ -107,12 +107,9 @@ struct ThemePreview: View { } #Preview { - let url = URL(string: "https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/catppuccin-frappe.itermcolors")! - let data = try! Data(contentsOf: url) - ThemePreview( hostsManager: HostsManager(), - theme: Theme.decodeTheme(data: data)!, + theme: .constant(Theme.defaultTheme), canModify: true ) .border(Color.red)