mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
added a themepreview to themeditor
added background to themeeditor extracted the raw themepreview to thempreview and made the previous view themebutton fixed padding above your themes header
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
A98554622E0587DF009051BD /* HostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsView.swift; sourceTree = "<group>"; };
|
||||
A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTerm.Color.swift; sourceTree = "<group>"; };
|
||||
A9BA1D1A2E1E81CA005BDCEF /* ThemePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreview.swift; sourceTree = "<group>"; };
|
||||
A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = "<group>"; };
|
||||
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
|
||||
A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||
A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManagerView.swift; sourceTree = "<group>"; };
|
||||
A9D8192E2E0F1BEE00442D38 /* ThemePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreview.swift; sourceTree = "<group>"; };
|
||||
A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = "<group>"; };
|
||||
A9D819302E102D8700442D38 /* HostkeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostkeysView.swift; sourceTree = "<group>"; };
|
||||
A9DA97702E0D30ED00142DDC /* HostSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbol.swift; sourceTree = "<group>"; };
|
||||
A9DA97722E0D40C100142DDC /* HostSymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbolPreview.swift; sourceTree = "<group>"; };
|
||||
@@ -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 = "<group>";
|
||||
@@ -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 */,
|
||||
|
||||
90
ShhShell/Views/Themes/ThemeButton.swift
Normal file
90
ShhShell/Views/Themes/ThemeButton.swift
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
List {
|
||||
Section("Name") {
|
||||
TextField("Name", text: $theme.name)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
|
||||
Section("Name") {
|
||||
TextField("Name", text: $theme.name)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
Section("Main Colors") {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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("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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 body: some View {
|
||||
VStack {
|
||||
Text(theme.name)
|
||||
.foregroundStyle(theme.foreground.suiColor)
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
|
||||
var isSelected: Bool {
|
||||
return hostsManager.isThemeSelected(theme)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user