diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample.xcodeproj/project.pbxproj b/Example/CodeEditTextViewExample/CodeEditTextViewExample.xcodeproj/project.pbxproj index 4a583aa28..d0ba091d5 100644 --- a/Example/CodeEditTextViewExample/CodeEditTextViewExample.xcodeproj/project.pbxproj +++ b/Example/CodeEditTextViewExample/CodeEditTextViewExample.xcodeproj/project.pbxproj @@ -8,27 +8,34 @@ /* Begin PBXBuildFile section */ 6C2265DF2D306AB7008710D7 /* CodeEditTextView in Frameworks */ = {isa = PBXBuildFile; productRef = 6C2265DE2D306AB7008710D7 /* CodeEditTextView */; }; - 6C2265E42D306B90008710D7 /* SwiftUITextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2265E32D306B90008710D7 /* SwiftUITextView.swift */; }; - 6C2265E62D306D37008710D7 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2265E52D306D37008710D7 /* TextViewController.swift */; }; - 6CCDA29B2D306A25007CD84A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CCDA2942D306A25007CD84A /* Assets.xcassets */; }; - 6CCDA29D2D306A25007CD84A /* CodeEditTextViewExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CCDA2952D306A25007CD84A /* CodeEditTextViewExampleApp.swift */; }; - 6CCDA29E2D306A25007CD84A /* CodeEditTextViewExampleDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CCDA2962D306A25007CD84A /* CodeEditTextViewExampleDocument.swift */; }; - 6CCDA29F2D306A25007CD84A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CCDA2972D306A25007CD84A /* ContentView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 6C2265E12D306B58008710D7 /* CodeEditTextViewExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CodeEditTextViewExample.entitlements; sourceTree = ""; }; - 6C2265E32D306B90008710D7 /* SwiftUITextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUITextView.swift; sourceTree = ""; }; - 6C2265E52D306D37008710D7 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; }; 6CCDA27D2D306A1B007CD84A /* CodeEditTextViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeEditTextViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 6CCDA2942D306A25007CD84A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 6CCDA2952D306A25007CD84A /* CodeEditTextViewExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditTextViewExampleApp.swift; sourceTree = ""; }; - 6CCDA2962D306A25007CD84A /* CodeEditTextViewExampleDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditTextViewExampleDocument.swift; sourceTree = ""; }; - 6CCDA2972D306A25007CD84A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 6CCDA2982D306A25007CD84A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6CCDA2A12D306A5B007CD84A /* CodeEditTextView */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CodeEditTextView; path = ../..; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + B6654F662DF001EB003B32B8 /* Exceptions for "CodeEditTextViewExample" folder in "CodeEditTextViewExample" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 6CCDA27C2D306A1B007CD84A /* CodeEditTextViewExample */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + B6654F5D2DF001EB003B32B8 /* CodeEditTextViewExample */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + B6654F662DF001EB003B32B8 /* Exceptions for "CodeEditTextViewExample" folder in "CodeEditTextViewExample" target */, + ); + path = CodeEditTextViewExample; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 6CCDA27A2D306A1B007CD84A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -41,29 +48,11 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 6C2265E02D306AEB008710D7 /* Documents */ = { - isa = PBXGroup; - children = ( - 6CCDA2962D306A25007CD84A /* CodeEditTextViewExampleDocument.swift */, - ); - path = Documents; - sourceTree = ""; - }; - 6C2265E22D306B69008710D7 /* Views */ = { - isa = PBXGroup; - children = ( - 6CCDA2972D306A25007CD84A /* ContentView.swift */, - 6C2265E32D306B90008710D7 /* SwiftUITextView.swift */, - 6C2265E52D306D37008710D7 /* TextViewController.swift */, - ); - path = Views; - sourceTree = ""; - }; 6CCDA2742D306A1B007CD84A = { isa = PBXGroup; children = ( 6CCDA2A12D306A5B007CD84A /* CodeEditTextView */, - 6CCDA2992D306A25007CD84A /* CodeEditTextViewExample */, + B6654F5D2DF001EB003B32B8 /* CodeEditTextViewExample */, 6CCDA2A02D306A5B007CD84A /* Frameworks */, 6CCDA27E2D306A1B007CD84A /* Products */, ); @@ -77,19 +66,6 @@ name = Products; sourceTree = ""; }; - 6CCDA2992D306A25007CD84A /* CodeEditTextViewExample */ = { - isa = PBXGroup; - children = ( - 6CCDA2952D306A25007CD84A /* CodeEditTextViewExampleApp.swift */, - 6C2265E02D306AEB008710D7 /* Documents */, - 6C2265E22D306B69008710D7 /* Views */, - 6CCDA2942D306A25007CD84A /* Assets.xcassets */, - 6CCDA2982D306A25007CD84A /* Info.plist */, - 6C2265E12D306B58008710D7 /* CodeEditTextViewExample.entitlements */, - ); - path = CodeEditTextViewExample; - sourceTree = ""; - }; 6CCDA2A02D306A5B007CD84A /* Frameworks */ = { isa = PBXGroup; children = ( @@ -112,6 +88,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + B6654F5D2DF001EB003B32B8 /* CodeEditTextViewExample */, + ); name = CodeEditTextViewExample; packageProductDependencies = ( 6C2265DE2D306AB7008710D7 /* CodeEditTextView */, @@ -159,7 +138,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6CCDA29B2D306A25007CD84A /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -170,11 +148,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6C2265E62D306D37008710D7 /* TextViewController.swift in Sources */, - 6CCDA29D2D306A25007CD84A /* CodeEditTextViewExampleApp.swift in Sources */, - 6CCDA29E2D306A25007CD84A /* CodeEditTextViewExampleDocument.swift in Sources */, - 6CCDA29F2D306A25007CD84A /* ContentView.swift in Sources */, - 6C2265E42D306B90008710D7 /* SwiftUITextView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-1024.png b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-1024.png new file mode 100644 index 000000000..86c5e4478 Binary files /dev/null and b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-1024.png differ diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-128.png b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-128.png new file mode 100644 index 000000000..af8e599cc Binary files /dev/null and b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-128.png differ diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-16.png b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-16.png new file mode 100644 index 000000000..2e3297530 Binary files /dev/null and b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-16.png differ diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-256.png b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-256.png new file mode 100644 index 000000000..b37995124 Binary files /dev/null and b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-256.png differ diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-32.png b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-32.png new file mode 100644 index 000000000..4ba6ba21d Binary files /dev/null and b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-32.png differ diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-512.png b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-512.png new file mode 100644 index 000000000..e35cfe804 Binary files /dev/null and b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-512.png differ diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-64.png b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-64.png new file mode 100644 index 000000000..e4cc199ba Binary files /dev/null and b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/CodeEditTextView-Icon-64.png differ diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json index 230588010..6d0b8c531 100644 --- a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,35 +1,68 @@ { "images" : [ { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" + "size" : "16x16", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "CodeEditTextView-Icon-1024.png", + "scale" : "2x" } ], "info" : { - "author" : "xcode", - "version" : 1 + "version" : 1, + "author" : "xcode" } -} +} \ No newline at end of file diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/ContentView.swift b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/ContentView.swift index 1a64d8b54..9cd2a6021 100644 --- a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/ContentView.swift +++ b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/ContentView.swift @@ -11,18 +11,28 @@ struct ContentView: View { @Binding var document: CodeEditTextViewExampleDocument @AppStorage("wraplines") private var wrapLines: Bool = true @AppStorage("edgeinsets") private var enableEdgeInsets: Bool = false + @AppStorage("usesystemcursor") private var useSystemCursor: Bool = false + @AppStorage("isselectable") private var isSelectable: Bool = true + @AppStorage("iseditable") private var isEditable: Bool = true var body: some View { - VStack(spacing: 0) { - HStack { - Toggle("Wrap Lines", isOn: $wrapLines) - Toggle("Inset Edges", isOn: $enableEdgeInsets) - } - Divider() - SwiftUITextView( + SwiftUITextView( + text: document.text, + wrapLines: $wrapLines, + enableEdgeInsets: $enableEdgeInsets, + useSystemCursor: $useSystemCursor, + isSelectable: $isSelectable, + isEditable: $isEditable + ) + .padding(.bottom, 28) + .overlay(alignment: .bottom) { + StatusBar( text: document.text, wrapLines: $wrapLines, - enableEdgeInsets: $enableEdgeInsets + enableEdgeInsets: $enableEdgeInsets, + useSystemCursor: $useSystemCursor, + isSelectable: $isSelectable, + isEditable: $isEditable ) } } diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/StatusBar.swift b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/StatusBar.swift new file mode 100644 index 000000000..211254072 --- /dev/null +++ b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/StatusBar.swift @@ -0,0 +1,63 @@ +// +// StatusBar.swift +// CodeEditTextViewExample +// +// Created by Austin Condiff on 6/3/25. +// + +import SwiftUI + +struct StatusBar: View { + @Environment(\.colorScheme) + var colorScheme + + var text: NSTextStorage + + @Binding var wrapLines: Bool + @Binding var enableEdgeInsets: Bool + @Binding var useSystemCursor: Bool + @Binding var isSelectable: Bool + @Binding var isEditable: Bool + + var body: some View { + HStack { + Menu { + Toggle("Wrap Lines", isOn: $wrapLines) + Toggle("Inset Edges", isOn: $enableEdgeInsets) + Toggle("Use System Cursor", isOn: $useSystemCursor) + Toggle("Selectable", isOn: $isSelectable) + Toggle("Editable", isOn: $isEditable) + } label: {} + .background { + Image(systemName: "switch.2") + .foregroundStyle(.secondary) + .font(.system(size: 13.5, weight: .regular)) + } + .menuStyle(.borderlessButton) + .menuIndicator(.hidden) + .frame(maxWidth: 18, alignment: .center) + Spacer() + Group { + Text("\(text.length) characters") + } + .foregroundStyle(.secondary) + } + .font(.subheadline) + .fontWeight(.medium) + .controlSize(.small) + .padding(.horizontal, 8) + .frame(height: 28) + .background(.bar) + .overlay(alignment: .top) { + VStack { + Divider() + .overlay { + if colorScheme == .dark { + Color.black + } + } + } + } + .zIndex(2) + } +} diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/SwiftUITextView.swift b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/SwiftUITextView.swift index 1bb836239..693826a39 100644 --- a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/SwiftUITextView.swift +++ b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/SwiftUITextView.swift @@ -13,17 +13,26 @@ struct SwiftUITextView: NSViewControllerRepresentable { var text: NSTextStorage @Binding var wrapLines: Bool @Binding var enableEdgeInsets: Bool + @Binding var useSystemCursor: Bool + @Binding var isSelectable: Bool + @Binding var isEditable: Bool func makeNSViewController(context: Context) -> TextViewController { let controller = TextViewController(string: "") controller.textView.setTextStorage(text) controller.wrapLines = wrapLines controller.enableEdgeInsets = enableEdgeInsets + controller.useSystemCursor = useSystemCursor + controller.isSelectable = isSelectable + controller.isEditable = isEditable return controller } func updateNSViewController(_ nsViewController: TextViewController, context: Context) { nsViewController.wrapLines = wrapLines nsViewController.enableEdgeInsets = enableEdgeInsets + nsViewController.useSystemCursor = useSystemCursor + nsViewController.isSelectable = isSelectable + nsViewController.isEditable = isEditable } } diff --git a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/TextViewController.swift b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/TextViewController.swift index a880d9731..a37704c8c 100644 --- a/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/TextViewController.swift +++ b/Example/CodeEditTextViewExample/CodeEditTextViewExample/Views/TextViewController.swift @@ -27,6 +27,25 @@ class TextViewController: NSViewController { textView.wrapLines = wrapLines } } + var useSystemCursor: Bool = false { + didSet { + textView.useSystemCursor = useSystemCursor + // Force cursor update by temporarily removing and re-adding the selection + if let range = textView.selectionManager.textSelections.first?.range { + textView.selectionManager.setSelectedRange(NSRange(location: range.location, length: 0)) + } + } + } + var isSelectable: Bool = true { + didSet { + textView.isSelectable = isSelectable + } + } + var isEditable: Bool = true { + didSet { + textView.isEditable = isEditable + } + } init(string: String) { textView = TextView(string: string)