@@ -64,7 +64,7 @@ public final class AppKitBackend: AppBackend {
64
64
backing: . buffered,
65
65
defer: true
66
66
)
67
- window. delegate = window. resizeDelegate
67
+ window. delegate = window. customDelegate
68
68
69
69
return window
70
70
}
@@ -94,7 +94,7 @@ public final class AppKitBackend: AppBackend {
94
94
ofWindow window: Window ,
95
95
to action: @escaping ( SIMD2 < Int > ) -> Void
96
96
) {
97
- window. resizeDelegate . setHandler ( action)
97
+ window. customDelegate . setHandler ( action)
98
98
}
99
99
100
100
public func setTitle( ofWindow window: Window , to title: String ) {
@@ -176,6 +176,9 @@ public final class AppKitBackend: AppBackend {
176
176
let about = NSMenuItem ( )
177
177
about. submenu = createDefaultAboutMenu ( )
178
178
menuBar. addItem ( about)
179
+ let edit = NSMenuItem ( )
180
+ edit. submenu = createDefaultEditMenu ( )
181
+ menuBar. addItem ( edit)
179
182
180
183
var helpMenu : NSMenu ?
181
184
for submenu in submenus {
@@ -230,6 +233,55 @@ public final class AppKitBackend: AppBackend {
230
233
return appMenu
231
234
}
232
235
236
+ public static func createDefaultEditMenu( ) -> NSMenu {
237
+ let appMenu = NSMenu ( title: " Edit " )
238
+ let undoItem = appMenu. addItem (
239
+ withTitle: " Undo " ,
240
+ action: " undo: " ,
241
+ keyEquivalent: " z "
242
+ )
243
+ undoItem. keyEquivalentModifierMask = . command
244
+
245
+ let redoItem = appMenu. addItem (
246
+ withTitle: " Redo " ,
247
+ action: " redo: " ,
248
+ keyEquivalent: " z "
249
+ )
250
+ redoItem. keyEquivalentModifierMask = [ . command, . shift]
251
+
252
+ appMenu. addItem ( NSMenuItem . separator ( ) )
253
+
254
+ let cutItem = appMenu. addItem (
255
+ withTitle: " Cut " ,
256
+ action: " cut: " ,
257
+ keyEquivalent: " x "
258
+ )
259
+ cutItem. keyEquivalentModifierMask = . command
260
+
261
+ let copyItem = appMenu. addItem (
262
+ withTitle: " Copy " ,
263
+ action: " copy: " ,
264
+ keyEquivalent: " c "
265
+ )
266
+ copyItem. keyEquivalentModifierMask = . command
267
+
268
+ let pasteItem = appMenu. addItem (
269
+ withTitle: " Paste " ,
270
+ action: " paste: " ,
271
+ keyEquivalent: " v "
272
+ )
273
+ pasteItem. keyEquivalentModifierMask = . command
274
+
275
+ let selectAllItem = appMenu. addItem (
276
+ withTitle: " Select all " ,
277
+ action: " selectAll: " ,
278
+ keyEquivalent: " a "
279
+ )
280
+ selectAllItem. keyEquivalentModifierMask = . command
281
+
282
+ return appMenu
283
+ }
284
+
233
285
public func setApplicationMenu( _ submenus: [ ResolvedMenu . Submenu ] ) {
234
286
let ( menuBar, helpMenu) = Self . renderMenuBar ( submenus)
235
287
NSApplication . shared. mainMenu = menuBar
@@ -448,7 +500,7 @@ public final class AppKitBackend: AppBackend {
448
500
449
501
public func size(
450
502
of text: String ,
451
- whenDisplayedIn textView : Widget ,
503
+ whenDisplayedIn widget : Widget ,
452
504
proposedFrame: SIMD2 < Int > ? ,
453
505
environment: EnvironmentValues
454
506
) -> SIMD2 < Int > {
@@ -458,7 +510,7 @@ public final class AppKitBackend: AppBackend {
458
510
// reaches zero width.
459
511
let size = size (
460
512
of: text,
461
- whenDisplayedIn: textView ,
513
+ whenDisplayedIn: widget ,
462
514
proposedFrame: SIMD2 ( 1 , proposedFrame. y) ,
463
515
environment: environment
464
516
)
@@ -676,7 +728,9 @@ public final class AppKitBackend: AppBackend {
676
728
textField. isEnabled = environment. isEnabled
677
729
textField. placeholderString = placeholder
678
730
textField. appearance = environment. colorScheme. nsAppearance
679
- textField. font = Self . font ( for: environment)
731
+ if textField. font != Self . font ( for: environment) {
732
+ textField. font = Self . font ( for: environment)
733
+ }
680
734
textField. onEdit = { textField in
681
735
onChange ( textField. stringValue)
682
736
}
@@ -709,6 +763,58 @@ public final class AppKitBackend: AppBackend {
709
763
textField. stringValue = content
710
764
}
711
765
766
+ public func createTextEditor( ) -> Widget {
767
+ let textEditor = NSObservableTextView ( )
768
+ textEditor. drawsBackground = false
769
+ textEditor. delegate = textEditor
770
+ textEditor. allowsUndo = true
771
+ textEditor. textContainerInset = . zero
772
+ textEditor. textContainer? . lineFragmentPadding = 0
773
+ return textEditor
774
+ }
775
+
776
+ public func updateTextEditor(
777
+ _ textEditor: Widget ,
778
+ environment: EnvironmentValues ,
779
+ onChange: @escaping ( String ) -> Void
780
+ ) {
781
+ let textEditor = textEditor as! NSObservableTextView
782
+ textEditor. onEdit = { textView in
783
+ onChange ( self . getContent ( ofTextEditor: textView) )
784
+ }
785
+ if textEditor. font != Self . font ( for: environment) {
786
+ textEditor. font = Self . font ( for: environment)
787
+ }
788
+ textEditor. appearance = environment. colorScheme. nsAppearance
789
+ textEditor. isEditable = environment. isEnabled
790
+
791
+ if #available( macOS 14 , * ) {
792
+ textEditor. contentType =
793
+ switch environment. textContentType {
794
+ case . url:
795
+ . URL
796
+ case . phoneNumber:
797
+ . telephoneNumber
798
+ case . name:
799
+ . name
800
+ case . emailAddress:
801
+ . emailAddress
802
+ case . text, . digits( _) , . decimal( _) :
803
+ nil
804
+ }
805
+ }
806
+ }
807
+
808
+ public func setContent( ofTextEditor textEditor: Widget , to content: String ) {
809
+ let textEditor = textEditor as! NSObservableTextView
810
+ textEditor. string = content
811
+ }
812
+
813
+ public func getContent( ofTextEditor textEditor: Widget ) -> String {
814
+ let textEditor = textEditor as! NSObservableTextView
815
+ return textEditor. string
816
+ }
817
+
712
818
public func createScrollContainer( for child: Widget ) -> Widget {
713
819
let scrollView = NSScrollView ( )
714
820
@@ -1756,6 +1862,14 @@ class NSObservableTextField: NSTextField {
1756
1862
}
1757
1863
}
1758
1864
1865
+ class NSObservableTextView : NSTextView , NSTextViewDelegate {
1866
+ func textDidChange( _ notification: Notification ) {
1867
+ onEdit ? ( self )
1868
+ }
1869
+
1870
+ var onEdit : ( ( NSTextView ) -> Void ) ?
1871
+ }
1872
+
1759
1873
// Source: https://gist.github.com/sindresorhus/3580ce9426fff8fafb1677341fca4815
1760
1874
extension NSControl {
1761
1875
typealias ActionClosure = ( ( NSControl ) -> Void )
@@ -1870,7 +1984,8 @@ class NSSplitViewResizingDelegate: NSObject, NSSplitViewDelegate {
1870
1984
}
1871
1985
1872
1986
public class NSCustomWindow : NSWindow {
1873
- var resizeDelegate = ResizeDelegate ( )
1987
+ var customDelegate = Delegate ( )
1988
+ var persistentUndoManager = UndoManager ( )
1874
1989
1875
1990
/// Allows the backing scale factor to be overridden. Useful for keeping
1876
1991
/// UI tests consistent across devices.
@@ -1882,7 +1997,7 @@ public class NSCustomWindow: NSWindow {
1882
1997
backingScaleFactorOverride ?? super. backingScaleFactor
1883
1998
}
1884
1999
1885
- class ResizeDelegate : NSObject , NSWindowDelegate {
2000
+ class Delegate : NSObject , NSWindowDelegate {
1886
2001
var resizeHandler : ( ( SIMD2 < Int > ) -> Void ) ?
1887
2002
1888
2003
func setHandler( _ resizeHandler: @escaping ( SIMD2 < Int > ) -> Void ) {
@@ -1907,6 +2022,10 @@ public class NSCustomWindow: NSWindow {
1907
2022
1908
2023
return frameSize
1909
2024
}
2025
+
2026
+ func windowWillReturnUndoManager( _ window: NSWindow ) -> UndoManager ? {
2027
+ ( window as! NSCustomWindow ) . persistentUndoManager
2028
+ }
1910
2029
}
1911
2030
}
1912
2031
0 commit comments