fix(editor): activate text editor for keyboard input
This commit is contained in:
parent
ce55b9a2ce
commit
a010cb19be
2 changed files with 64 additions and 1 deletions
|
|
@ -9,8 +9,16 @@ import SaplingEditor
|
|||
import SaplingRenderer
|
||||
import SaplingUI
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
@main
|
||||
struct SaplingApplication: App {
|
||||
#if os(macOS)
|
||||
@NSApplicationDelegateAdaptor(SaplingAppDelegate.self) private var appDelegate
|
||||
#endif
|
||||
|
||||
@StateObject private var model = SaplingAppModel(dependencies: .live())
|
||||
|
||||
var body: some Scene {
|
||||
|
|
@ -33,6 +41,15 @@ struct SaplingApplication: App {
|
|||
}
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private final class SaplingAppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@MainActor
|
||||
private final class SaplingAppModel: ObservableObject {
|
||||
@Published var workspace: Workspace
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ private struct NativeMarkdownTextView: NSViewRepresentable {
|
|||
layoutManager.addTextContainer(textContainer)
|
||||
textStorage.addLayoutManager(layoutManager)
|
||||
|
||||
let textView = NSTextView(frame: scrollView.contentView.bounds, textContainer: textContainer)
|
||||
let textView = EditorTextView(frame: scrollView.contentView.bounds, textContainer: textContainer)
|
||||
textView.autoresizingMask = [.width]
|
||||
textView.minSize = NSSize(width: 0, height: scrollView.contentSize.height)
|
||||
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
||||
|
|
@ -160,6 +160,8 @@ private struct NativeMarkdownTextView: NSViewRepresentable {
|
|||
textView.delegate = context.coordinator
|
||||
textView.string = text
|
||||
textView.isRichText = false
|
||||
textView.isEditable = true
|
||||
textView.isSelectable = true
|
||||
textView.isAutomaticQuoteSubstitutionEnabled = false
|
||||
textView.isAutomaticDashSubstitutionEnabled = false
|
||||
textView.isAutomaticTextReplacementEnabled = false
|
||||
|
|
@ -179,6 +181,7 @@ private struct NativeMarkdownTextView: NSViewRepresentable {
|
|||
scrollView.editorTextView = textView
|
||||
scrollView.updateEditorInsets()
|
||||
context.coordinator.applyHybridAttributes(to: textView)
|
||||
context.coordinator.requestInitialFocus(for: textView)
|
||||
return scrollView
|
||||
}
|
||||
|
||||
|
|
@ -199,6 +202,7 @@ private struct NativeMarkdownTextView: NSViewRepresentable {
|
|||
}
|
||||
|
||||
context.coordinator.applyHybridAttributes(to: textView)
|
||||
context.coordinator.requestInitialFocus(for: textView)
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, NSTextViewDelegate {
|
||||
|
|
@ -206,6 +210,7 @@ private struct NativeMarkdownTextView: NSViewRepresentable {
|
|||
private var programmaticUpdateDepth = 0
|
||||
private var lastStyledText: String?
|
||||
private var lastStyledActiveLineIndex: Int?
|
||||
private var didFocusTextView = false
|
||||
|
||||
init(_ parent: NativeMarkdownTextView) {
|
||||
self.parent = parent
|
||||
|
|
@ -263,6 +268,24 @@ private struct NativeMarkdownTextView: NSViewRepresentable {
|
|||
}
|
||||
}
|
||||
|
||||
func requestInitialFocus(for textView: NSTextView) {
|
||||
guard !didFocusTextView else { return }
|
||||
|
||||
DispatchQueue.main.async { [weak self, weak textView] in
|
||||
guard let self,
|
||||
let textView,
|
||||
let window = textView.window,
|
||||
!self.didFocusTextView
|
||||
else { return }
|
||||
|
||||
if window.firstResponder !== textView {
|
||||
window.makeFirstResponder(textView)
|
||||
}
|
||||
|
||||
self.didFocusTextView = window.firstResponder === textView
|
||||
}
|
||||
}
|
||||
|
||||
func performProgrammaticUpdate(_ updates: () -> Void) {
|
||||
programmaticUpdateDepth += 1
|
||||
defer {
|
||||
|
|
@ -281,6 +304,12 @@ private struct NativeMarkdownTextView: NSViewRepresentable {
|
|||
}
|
||||
}
|
||||
|
||||
private final class EditorTextView: NSTextView {
|
||||
override var acceptsFirstResponder: Bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private final class ComfortableEditorScrollView: NSScrollView {
|
||||
weak var editorTextView: NSTextView?
|
||||
|
||||
|
|
@ -323,6 +352,7 @@ private struct NativeMarkdownTextView: UIViewRepresentable {
|
|||
textView.textContainerInset = UIEdgeInsets(top: 28, left: 24, bottom: 28, right: 24)
|
||||
textView.backgroundColor = .systemBackground
|
||||
context.coordinator.applyHybridAttributes(to: textView)
|
||||
context.coordinator.requestInitialFocus(for: textView)
|
||||
return textView
|
||||
}
|
||||
|
||||
|
|
@ -334,6 +364,7 @@ private struct NativeMarkdownTextView: UIViewRepresentable {
|
|||
}
|
||||
}
|
||||
context.coordinator.applyHybridAttributes(to: textView)
|
||||
context.coordinator.requestInitialFocus(for: textView)
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, UITextViewDelegate {
|
||||
|
|
@ -341,6 +372,7 @@ private struct NativeMarkdownTextView: UIViewRepresentable {
|
|||
private var programmaticUpdateDepth = 0
|
||||
private var lastStyledText: String?
|
||||
private var lastStyledActiveLineIndex: Int?
|
||||
private var didFocusTextView = false
|
||||
|
||||
init(_ parent: NativeMarkdownTextView) {
|
||||
self.parent = parent
|
||||
|
|
@ -395,6 +427,20 @@ private struct NativeMarkdownTextView: UIViewRepresentable {
|
|||
updates()
|
||||
}
|
||||
|
||||
func requestInitialFocus(for textView: UITextView) {
|
||||
guard !didFocusTextView else { return }
|
||||
|
||||
DispatchQueue.main.async { [weak self, weak textView] in
|
||||
guard let self,
|
||||
let textView,
|
||||
textView.window != nil,
|
||||
!self.didFocusTextView
|
||||
else { return }
|
||||
|
||||
self.didFocusTextView = textView.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
private var isPerformingProgrammaticUpdate: Bool {
|
||||
programmaticUpdateDepth > 0
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue