mirror of
https://github.com/h3pdesign/Neon-Vision-Editor
synced 2026-04-21 21:37:17 +00:00
491 lines
18 KiB
Swift
491 lines
18 KiB
Swift
import SwiftUI
|
||
#if os(macOS)
|
||
import AppKit
|
||
#elseif os(iOS)
|
||
import UIKit
|
||
#endif
|
||
|
||
extension ContentView {
|
||
#if os(iOS)
|
||
private var isIPadToolbarLayout: Bool {
|
||
UIDevice.current.userInterfaceIdiom == .pad && horizontalSizeClass == .regular
|
||
}
|
||
|
||
private var iPadToolbarMaxWidth: CGFloat {
|
||
let screenWidth = UIApplication.shared.connectedScenes
|
||
.compactMap { $0 as? UIWindowScene }
|
||
.first(where: { $0.activationState == .foregroundActive })?
|
||
.screen.bounds.width ?? 1024
|
||
let target = screenWidth * 0.72
|
||
return min(max(target, 560), 980)
|
||
}
|
||
|
||
private var iPadPromotedActionsCount: Int {
|
||
switch iPadToolbarMaxWidth {
|
||
case 920...: return 7
|
||
case 840...: return 6
|
||
case 760...: return 5
|
||
case 680...: return 4
|
||
case 620...: return 3
|
||
default: return 2
|
||
}
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var newTabControl: some View {
|
||
Button(action: { viewModel.addNewTab() }) {
|
||
Image(systemName: "plus.square.on.square")
|
||
}
|
||
.help("New Tab")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var languagePickerControl: some View {
|
||
Picker("Language", selection: currentLanguagePickerBinding) {
|
||
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "cobol", "dotenv", "proto", "graphql", "rst", "nginx", "sql", "html", "css", "c", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
|
||
let label: String = {
|
||
switch lang {
|
||
case "php": return "PHP"
|
||
case "cobol": return "COBOL"
|
||
case "dotenv": return "Dotenv"
|
||
case "proto": return "Proto"
|
||
case "graphql": return "GraphQL"
|
||
case "rst": return "reStructuredText"
|
||
case "nginx": return "Nginx"
|
||
case "objective-c": return "Objective-C"
|
||
case "csharp": return "C#"
|
||
case "c": return "C"
|
||
case "cpp": return "C++"
|
||
case "json": return "JSON"
|
||
case "xml": return "XML"
|
||
case "yaml": return "YAML"
|
||
case "toml": return "TOML"
|
||
case "csv": return "CSV"
|
||
case "ini": return "INI"
|
||
case "sql": return "SQL"
|
||
case "vim": return "Vim"
|
||
case "log": return "Log"
|
||
case "ipynb": return "Jupyter Notebook"
|
||
case "html": return "HTML"
|
||
case "css": return "CSS"
|
||
case "standard": return "Standard"
|
||
default: return lang.capitalized
|
||
}
|
||
}()
|
||
Text(label).tag(lang)
|
||
}
|
||
}
|
||
.labelsHidden()
|
||
.help("Language")
|
||
.frame(width: isIPadToolbarLayout ? 160 : 120)
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var aiSelectorControl: some View {
|
||
Button(action: {
|
||
showAISelectorPopover.toggle()
|
||
}) {
|
||
Image(systemName: "brain.head.profile")
|
||
}
|
||
.help("AI Model & Settings")
|
||
.popover(isPresented: $showAISelectorPopover) {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
Text("AI Model").font(.headline)
|
||
Picker("AI Model", selection: $selectedModel) {
|
||
HStack(spacing: 6) {
|
||
Image(systemName: "brain.head.profile")
|
||
Text("Apple Intelligence")
|
||
}
|
||
.tag(AIModel.appleIntelligence)
|
||
Text("Grok").tag(AIModel.grok)
|
||
Text("OpenAI").tag(AIModel.openAI)
|
||
Text("Gemini").tag(AIModel.gemini)
|
||
Text("Anthropic").tag(AIModel.anthropic)
|
||
}
|
||
.labelsHidden()
|
||
.frame(width: 170)
|
||
.controlSize(.large)
|
||
|
||
Button("API Settings…") {
|
||
showAISelectorPopover = false
|
||
showAPISettings = true
|
||
}
|
||
.buttonStyle(.bordered)
|
||
}
|
||
.padding(12)
|
||
}
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var activeProviderBadgeControl: some View {
|
||
Text(activeProviderName)
|
||
.font(.caption)
|
||
.foregroundColor(.secondary)
|
||
.padding(.horizontal, 6)
|
||
.padding(.vertical, 2)
|
||
.background(Color.secondary.opacity(0.12), in: Capsule())
|
||
.help("Active provider")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var clearEditorControl: some View {
|
||
Button(action: {
|
||
clearEditorContent()
|
||
}) {
|
||
Image(systemName: "trash")
|
||
}
|
||
.help("Clear Editor")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var insertTemplateControl: some View {
|
||
Button(action: { insertTemplateForCurrentLanguage() }) {
|
||
Image(systemName: "doc.badge.plus")
|
||
}
|
||
.help("Insert Template for Current Language")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var openFileControl: some View {
|
||
Button(action: { openFileFromToolbar() }) {
|
||
Image(systemName: "folder")
|
||
}
|
||
.help("Open File…")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var saveFileControl: some View {
|
||
Button(action: { saveCurrentTabFromToolbar() }) {
|
||
Image(systemName: "square.and.arrow.down")
|
||
}
|
||
.disabled(viewModel.selectedTab == nil)
|
||
.help("Save File")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var toggleSidebarControl: some View {
|
||
Button(action: { toggleSidebarFromToolbar() }) {
|
||
Image(systemName: "sidebar.left")
|
||
}
|
||
.help("Toggle Sidebar")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var toggleProjectSidebarControl: some View {
|
||
Button(action: { showProjectStructureSidebar.toggle() }) {
|
||
Image(systemName: "sidebar.right")
|
||
}
|
||
.help("Toggle Project Structure Sidebar")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var findReplaceControl: some View {
|
||
Button(action: { showFindReplace = true }) {
|
||
Image(systemName: "magnifyingglass")
|
||
}
|
||
.help("Find & Replace")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var lineWrapControl: some View {
|
||
Button(action: { viewModel.isLineWrapEnabled.toggle() }) {
|
||
Image(systemName: viewModel.isLineWrapEnabled ? "text.justify" : "text.alignleft")
|
||
}
|
||
.help(viewModel.isLineWrapEnabled ? "Disable Wrap" : "Enable Wrap")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var autoCompletionControl: some View {
|
||
Button(action: { toggleAutoCompletion() }) {
|
||
Image(systemName: isAutoCompletionEnabled ? "bolt.horizontal.circle.fill" : "bolt.horizontal.circle")
|
||
}
|
||
.help(isAutoCompletionEnabled ? "Disable Code Completion" : "Enable Code Completion")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var iPadPromotedActions: some View {
|
||
if iPadPromotedActionsCount >= 1 { openFileControl }
|
||
if iPadPromotedActionsCount >= 2 { saveFileControl }
|
||
if iPadPromotedActionsCount >= 3 { toggleSidebarControl }
|
||
if iPadPromotedActionsCount >= 4 { toggleProjectSidebarControl }
|
||
if iPadPromotedActionsCount >= 5 { findReplaceControl }
|
||
if iPadPromotedActionsCount >= 6 { lineWrapControl }
|
||
if iPadPromotedActionsCount >= 7 { autoCompletionControl }
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var moreActionsControl: some View {
|
||
Menu {
|
||
Button(action: { toggleAutoCompletion() }) {
|
||
Label(isAutoCompletionEnabled ? "Disable Code Completion" : "Enable Code Completion", systemImage: isAutoCompletionEnabled ? "bolt.horizontal.circle.fill" : "bolt.horizontal.circle")
|
||
}
|
||
|
||
Button(action: { insertTemplateForCurrentLanguage() }) {
|
||
Label("Insert Template", systemImage: "doc.badge.plus")
|
||
}
|
||
|
||
Button(action: { openFileFromToolbar() }) {
|
||
Label("Open File…", systemImage: "folder")
|
||
}
|
||
|
||
Button(action: { saveCurrentTabFromToolbar() }) {
|
||
Label("Save File", systemImage: "square.and.arrow.down")
|
||
}
|
||
.disabled(viewModel.selectedTab == nil)
|
||
|
||
Button(action: { toggleSidebarFromToolbar() }) {
|
||
Label("Toggle Sidebar", systemImage: "sidebar.left")
|
||
}
|
||
|
||
Button(action: { showProjectStructureSidebar.toggle() }) {
|
||
Label("Toggle Project Structure Sidebar", systemImage: "sidebar.right")
|
||
}
|
||
|
||
Button(action: { showFindReplace = true }) {
|
||
Label("Find & Replace", systemImage: "magnifyingglass")
|
||
}
|
||
|
||
Button(action: {
|
||
viewModel.isBrainDumpMode.toggle()
|
||
UserDefaults.standard.set(viewModel.isBrainDumpMode, forKey: "BrainDumpModeEnabled")
|
||
}) {
|
||
Label("Brain Dump Mode", systemImage: "note.text")
|
||
}
|
||
|
||
Button(action: {
|
||
enableTranslucentWindow.toggle()
|
||
UserDefaults.standard.set(enableTranslucentWindow, forKey: "EnableTranslucentWindow")
|
||
NotificationCenter.default.post(name: .toggleTranslucencyRequested, object: enableTranslucentWindow)
|
||
}) {
|
||
Label("Translucent Window Background", systemImage: enableTranslucentWindow ? "rectangle.fill" : "rectangle")
|
||
}
|
||
|
||
Button(action: { viewModel.isLineWrapEnabled.toggle() }) {
|
||
Label(viewModel.isLineWrapEnabled ? "Disable Wrap" : "Enable Wrap", systemImage: viewModel.isLineWrapEnabled ? "text.justify" : "text.alignleft")
|
||
}
|
||
} label: {
|
||
Image(systemName: "ellipsis.circle")
|
||
}
|
||
.help("More Actions")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var iOSToolbarControls: some View {
|
||
languagePickerControl
|
||
newTabControl
|
||
aiSelectorControl
|
||
activeProviderBadgeControl
|
||
clearEditorControl
|
||
moreActionsControl
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var iPadDistributedToolbarControls: some View {
|
||
languagePickerControl
|
||
newTabControl
|
||
Spacer(minLength: 18)
|
||
iPadPromotedActions
|
||
Spacer(minLength: 18)
|
||
aiSelectorControl
|
||
activeProviderBadgeControl
|
||
Spacer(minLength: 18)
|
||
clearEditorControl
|
||
moreActionsControl
|
||
}
|
||
#endif
|
||
|
||
@ToolbarContentBuilder
|
||
var editorToolbarContent: some ToolbarContent {
|
||
#if os(iOS)
|
||
ToolbarItemGroup(placement: .topBarTrailing) {
|
||
HStack(spacing: 14) {
|
||
if isIPadToolbarLayout {
|
||
iPadDistributedToolbarControls
|
||
} else {
|
||
iOSToolbarControls
|
||
}
|
||
}
|
||
.frame(maxWidth: isIPadToolbarLayout ? iPadToolbarMaxWidth : .infinity, alignment: .trailing)
|
||
}
|
||
#else
|
||
ToolbarItemGroup(placement: .automatic) {
|
||
Picker("Language", selection: currentLanguagePickerBinding) {
|
||
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "cobol", "dotenv", "proto", "graphql", "rst", "nginx", "sql", "html", "css", "c", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
|
||
let label: String = {
|
||
switch lang {
|
||
case "php": return "PHP"
|
||
case "cobol": return "COBOL"
|
||
case "dotenv": return "Dotenv"
|
||
case "proto": return "Proto"
|
||
case "graphql": return "GraphQL"
|
||
case "rst": return "reStructuredText"
|
||
case "nginx": return "Nginx"
|
||
case "objective-c": return "Objective‑C"
|
||
case "csharp": return "C#"
|
||
case "c": return "C"
|
||
case "cpp": return "C++"
|
||
case "json": return "JSON"
|
||
case "xml": return "XML"
|
||
case "yaml": return "YAML"
|
||
case "toml": return "TOML"
|
||
case "csv": return "CSV"
|
||
case "ini": return "INI"
|
||
case "sql": return "SQL"
|
||
case "vim": return "Vim"
|
||
case "log": return "Log"
|
||
case "ipynb": return "Jupyter Notebook"
|
||
case "html": return "HTML"
|
||
case "css": return "CSS"
|
||
case "standard": return "Standard"
|
||
default: return lang.capitalized
|
||
}
|
||
}()
|
||
Text(label).tag(lang)
|
||
}
|
||
}
|
||
.labelsHidden()
|
||
.help("Language")
|
||
.controlSize(.large)
|
||
.frame(width: 140)
|
||
.padding(.vertical, 2)
|
||
|
||
Button(action: {
|
||
showAISelectorPopover.toggle()
|
||
}) {
|
||
Image(systemName: "brain.head.profile")
|
||
}
|
||
.help("AI Model & Settings")
|
||
.popover(isPresented: $showAISelectorPopover) {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
Text("AI Model").font(.headline)
|
||
Picker("AI Model", selection: $selectedModel) {
|
||
HStack(spacing: 6) {
|
||
Image(systemName: "brain.head.profile")
|
||
Text("Apple Intelligence")
|
||
}
|
||
.tag(AIModel.appleIntelligence)
|
||
Text("Grok").tag(AIModel.grok)
|
||
Text("OpenAI").tag(AIModel.openAI)
|
||
Text("Gemini").tag(AIModel.gemini)
|
||
Text("Anthropic").tag(AIModel.anthropic)
|
||
}
|
||
.labelsHidden()
|
||
.frame(width: 170)
|
||
.controlSize(.large)
|
||
|
||
Button("API Settings…") {
|
||
showAISelectorPopover = false
|
||
showAPISettings = true
|
||
}
|
||
.buttonStyle(.bordered)
|
||
}
|
||
.padding(12)
|
||
}
|
||
|
||
Text(activeProviderName)
|
||
.font(.caption)
|
||
.foregroundColor(.secondary)
|
||
.padding(.horizontal, 6)
|
||
.padding(.vertical, 2)
|
||
.background(Color.secondary.opacity(0.12), in: Capsule())
|
||
.help("Active provider")
|
||
|
||
Button(action: {
|
||
clearEditorContent()
|
||
}) {
|
||
Image(systemName: "trash")
|
||
}
|
||
.help("Clear Editor")
|
||
|
||
Button(action: {
|
||
insertTemplateForCurrentLanguage()
|
||
}) {
|
||
Image(systemName: "doc.badge.plus")
|
||
}
|
||
.help("Insert Template for Current Language")
|
||
|
||
Button(action: {
|
||
toggleAutoCompletion()
|
||
}) {
|
||
Image(systemName: isAutoCompletionEnabled ? "bolt.horizontal.circle.fill" : "bolt.horizontal.circle")
|
||
}
|
||
.help(isAutoCompletionEnabled ? "Disable Code Completion" : "Enable Code Completion")
|
||
|
||
Button(action: { openFileFromToolbar() }) {
|
||
Image(systemName: "folder")
|
||
}
|
||
.help("Open File…")
|
||
|
||
Button(action: { viewModel.addNewTab() }) {
|
||
Image(systemName: "plus.square.on.square")
|
||
}
|
||
.help("New Tab")
|
||
|
||
#if os(macOS)
|
||
Button(action: {
|
||
openWindow(id: "blank-window")
|
||
}) {
|
||
Image(systemName: "macwindow.badge.plus")
|
||
}
|
||
.help("New Window")
|
||
#endif
|
||
|
||
Button(action: {
|
||
saveCurrentTabFromToolbar()
|
||
}) {
|
||
Image(systemName: "square.and.arrow.down")
|
||
}
|
||
.disabled(viewModel.selectedTab == nil)
|
||
.help("Save File")
|
||
|
||
Button(action: {
|
||
toggleSidebarFromToolbar()
|
||
}) {
|
||
Image(systemName: "sidebar.left")
|
||
.symbolVariant(viewModel.showSidebar ? .fill : .none)
|
||
}
|
||
.help("Toggle Sidebar")
|
||
|
||
Button(action: {
|
||
showProjectStructureSidebar.toggle()
|
||
}) {
|
||
Image(systemName: "sidebar.right")
|
||
.symbolVariant(showProjectStructureSidebar ? .fill : .none)
|
||
}
|
||
.help("Toggle Project Structure Sidebar")
|
||
|
||
Button(action: {
|
||
showFindReplace = true
|
||
}) {
|
||
Image(systemName: "magnifyingglass")
|
||
}
|
||
.help("Find & Replace")
|
||
|
||
Button(action: {
|
||
viewModel.isBrainDumpMode.toggle()
|
||
UserDefaults.standard.set(viewModel.isBrainDumpMode, forKey: "BrainDumpModeEnabled")
|
||
}) {
|
||
Image(systemName: viewModel.isBrainDumpMode ? "note.text" : "note.text")
|
||
.symbolVariant(viewModel.isBrainDumpMode ? .fill : .none)
|
||
}
|
||
.help("Brain Dump Mode")
|
||
.accessibilityLabel("Brain Dump Mode")
|
||
|
||
Button(action: {
|
||
enableTranslucentWindow.toggle()
|
||
UserDefaults.standard.set(enableTranslucentWindow, forKey: "EnableTranslucentWindow")
|
||
NotificationCenter.default.post(name: .toggleTranslucencyRequested, object: enableTranslucentWindow)
|
||
}) {
|
||
Image(systemName: enableTranslucentWindow ? "rectangle.fill" : "rectangle")
|
||
}
|
||
.help("Toggle Translucent Window Background")
|
||
.accessibilityLabel("Translucent Window Background")
|
||
|
||
Button(action: { viewModel.isLineWrapEnabled.toggle() }) {
|
||
Image(systemName: viewModel.isLineWrapEnabled ? "text.justify" : "text.alignleft")
|
||
}
|
||
.help(viewModel.isLineWrapEnabled ? "Disable Wrap" : "Enable Wrap")
|
||
}
|
||
#endif
|
||
}
|
||
}
|