8
8
//
9
9
// ---------------------------------------------------------------------------
10
10
//
11
- // © 2018-2023 1024jp
11
+ // © 2018-2024 1024jp
12
12
//
13
13
// Licensed under the Apache License, Version 2.0 (the "License");
14
14
// you may not use this file except in compliance with the License.
@@ -29,13 +29,14 @@ import AppKit
29
29
30
30
var insertionLocations : [ Int ] { get set }
31
31
var selectionOrigins : [ Int ] { get set }
32
-
33
- var insertionPointTimer : ( any DispatchSourceTimer ) ? { get set }
34
- var insertionPointOn : Bool { get set }
35
32
var isPerformingRectangularSelection : Bool { get }
36
33
37
34
@available ( macOS 14 , * )
38
35
var insertionIndicators : [ NSTextInsertionIndicator ] { get set }
36
+
37
+ @available ( macOS, deprecated: 14 )
38
+ var insertionPointTimer : ( any DispatchSourceTimer ) ? { get set }
39
+ var insertionPointOn : Bool { get set }
39
40
}
40
41
41
42
@@ -58,13 +59,6 @@ extension MultiCursorEditing {
58
59
}
59
60
60
61
61
- /// Whether the receiver needs to draw insertion points by itself.
62
- var needsDrawInsertionPoints : Bool {
63
-
64
- self . insertionPointTimer? . isCancelled == false
65
- }
66
-
67
-
68
62
/// Inserts the same string at multiple ranges.
69
63
///
70
64
/// - Parameters:
@@ -314,20 +308,6 @@ extension MultiCursorEditing {
314
308
}
315
309
316
310
317
- /// Enables or disables `insertionPointTimer` according to the selection state.
318
- @available ( macOS, deprecated: 14 )
319
- func updateInsertionPointTimer( ) {
320
-
321
- if #available( macOS 14 , * ) { return }
322
-
323
- if self . isPerformingRectangularSelection || ( !self . insertionLocations. isEmpty && self . selectedRanges. allSatisfy ( { !$0. rangeValue. isEmpty } ) ) {
324
- self . enableOwnInsertionPointTimer ( )
325
- } else {
326
- self . insertionPointTimer? . cancel ( )
327
- }
328
- }
329
-
330
-
331
311
/// Adds new insertion points just above/below to the current insertions.
332
312
///
333
313
/// - Parameter affinity: The direction to add new ones; `.downstream` to add above, otherwise `.upstream`.
@@ -410,6 +390,8 @@ extension MultiCursorEditing {
410
390
411
391
guard !self . insertionLocations. isEmpty || !self . insertionIndicators. isEmpty else { return }
412
392
393
+ guard let layoutManager = self . layoutManager else { return assertionFailure ( ) }
394
+
413
395
let properInsertionLocations = ( self . isPerformingRectangularSelection && self . selectedRange. isEmpty) ? [ self . selectedRange. location] : [ ]
414
396
let insertionLocations = ( self . insertionLocations + properInsertionLocations)
415
397
@@ -418,7 +400,8 @@ extension MultiCursorEditing {
418
400
let isActive = ( self . window? . firstResponder == self && NSApp . isActive)
419
401
420
402
self . insertionIndicators = insertionLocations
421
- . compactMap { self . insertionPointRects ( at: $0) . first } // ignore split cursors
403
+ . compactMap { layoutManager. insertionPointRect ( at: $0) } // ignore split cursors
404
+ . map { $0. offset ( by: self . textContainerOrigin) }
422
405
. map { rect in
423
406
if let indicator = indicators. popFirst ( ) {
424
407
indicator. frame = rect
@@ -433,53 +416,13 @@ extension MultiCursorEditing {
433
416
}
434
417
435
418
// remove remaining indicators
436
- for indicator in indicators {
437
- indicator. removeFromSuperview ( )
438
- }
419
+ indicators. forEach { $0. removeFromSuperview ( ) }
439
420
}
440
421
}
441
422
442
423
443
-
444
- /// Workaround subclass to let NSTextView uses the new NSTextInsertionIndicator (FB12964810).
445
- @available ( macOS, deprecated: 14 , message: " Just remove this subclass and also all the codes related to insertion point drawing. " )
446
- final class LegacyEditorTextView : EditorTextView {
447
-
448
- override func drawInsertionPoint( in rect: NSRect , color: NSColor , turnedOn flag: Bool ) {
449
-
450
- super. drawInsertionPoint ( in: rect, color: color, turnedOn: flag)
451
-
452
- // draw sub insertion rects
453
- self . insertionLocations
454
- . flatMap { self . insertionPointRects ( at: $0) }
455
- . forEach { super. drawInsertionPoint ( in: $0, color: color, turnedOn: flag) }
456
- }
457
- }
458
-
459
-
460
-
461
424
extension NSTextView {
462
425
463
- /// Calculates rect for insertion point at `index`.
464
- ///
465
- /// - Parameter index: The character index where the insertion point will locate.
466
- /// - Returns: Rect where insertion point filled.
467
- final func insertionPointRects( at index: Int ) -> [ NSRect ] {
468
-
469
- guard let layoutManager = self . layoutManager else { assertionFailure ( ) ; return [ ] }
470
-
471
- let scale = self . scale
472
- return layoutManager. insertionPointRects ( at: index)
473
- . map { $0. offset ( by: self . textContainerOrigin) }
474
- . map { rect in
475
- NSRect ( x: ( rect. minX * scale) . rounded ( . down) / scale,
476
- y: rect. minY,
477
- width: 1 / scale,
478
- height: rect. height)
479
- }
480
- }
481
-
482
-
483
426
/// Finds the location for the insertion point where one (visual) line above to the given insertion point location.
484
427
///
485
428
/// - Parameter index: The character index of the reference insertion point.
@@ -524,52 +467,6 @@ extension NSTextView {
524
467
525
468
// MARK: Private
526
469
527
- private struct BlinkPeriod {
528
-
529
- var on : Int
530
- var off : Int
531
- }
532
-
533
-
534
- private extension UserDefaults {
535
-
536
- var textInsertionPointBlinkPeriod : BlinkPeriod {
537
-
538
- let onPeriod = self . integer ( forKey: " NSTextInsertionPointBlinkPeriodOn " )
539
- let offPeriod = self . integer ( forKey: " NSTextInsertionPointBlinkPeriodOff " )
540
-
541
- return BlinkPeriod ( on: ( onPeriod > 0 ) ? onPeriod : 500 ,
542
- off: ( offPeriod > 0 ) ? offPeriod : 500 )
543
- }
544
- }
545
-
546
-
547
- private extension MultiCursorEditing {
548
-
549
- /// Enables insertion point blink timer to draw insertion points forcibly.
550
- @available ( macOS, deprecated: 14 )
551
- private func enableOwnInsertionPointTimer( ) {
552
-
553
- guard self . insertionPointTimer? . isCancelled ?? true else { return }
554
-
555
- let period = UserDefaults . standard. textInsertionPointBlinkPeriod
556
-
557
- let timer = DispatchSource . makeTimerSource ( queue: . main)
558
- timer. schedule ( deadline: . now( ) )
559
- timer. setEventHandler { [ unowned self] in
560
- self . insertionPointOn. toggle ( )
561
- let interval = self . insertionPointOn ? period. on : period. off
562
- timer. schedule ( deadline: . now( ) + . milliseconds( interval) )
563
- self . setNeedsDisplay ( self . visibleRect, avoidAdditionalLayout: true )
564
- }
565
- timer. resume ( )
566
-
567
- self . insertionPointTimer? . cancel ( )
568
- self . insertionPointTimer = timer
569
- }
570
- }
571
-
572
-
573
470
private extension NSLayoutManager {
574
471
575
472
/// Returns the bounds between upper and lower bounds of the given `range` in horizontal axis.
@@ -629,3 +526,110 @@ private extension NSLayoutManager {
629
526
return rects. unique
630
527
}
631
528
}
529
+
530
+
531
+
532
+ // MARK: - LegacyEditorTextView
533
+
534
+ /// Workaround subclass to let NSTextView uses the new NSTextInsertionIndicator (FB12964810).
535
+ @available ( macOS, deprecated: 14 , message: " Just remove this subclass and also all the codes related to insertion point drawing. " )
536
+ final class LegacyEditorTextView : EditorTextView {
537
+
538
+ override func drawInsertionPoint( in rect: NSRect , color: NSColor , turnedOn flag: Bool ) {
539
+
540
+ super. drawInsertionPoint ( in: rect, color: color, turnedOn: flag)
541
+
542
+ // draw sub insertion rects
543
+ self . insertionLocations
544
+ . flatMap { self . insertionPointRects ( at: $0) }
545
+ . forEach { super. drawInsertionPoint ( in: $0, color: color, turnedOn: flag) }
546
+ }
547
+ }
548
+
549
+
550
+ @available ( macOS, deprecated: 14 )
551
+ extension MultiCursorEditing {
552
+
553
+ /// Whether the receiver needs to draw insertion points by itself.
554
+ var needsDrawInsertionPoints : Bool {
555
+
556
+ self . insertionPointTimer? . isCancelled == false
557
+ }
558
+
559
+
560
+ /// Enables or disables `insertionPointTimer` according to the selection state.
561
+ func updateInsertionPointTimer( ) {
562
+
563
+ if #available( macOS 14 , * ) { return }
564
+
565
+ if self . isPerformingRectangularSelection || ( !self . insertionLocations. isEmpty && self . selectedRanges. allSatisfy ( { !$0. rangeValue. isEmpty } ) ) {
566
+ self . enableOwnInsertionPointTimer ( )
567
+ } else {
568
+ self . insertionPointTimer? . cancel ( )
569
+ }
570
+ }
571
+
572
+
573
+ /// Calculates rect for insertion point at `index`.
574
+ ///
575
+ /// - Parameter index: The character index where the insertion point will locate.
576
+ /// - Returns: Rect where insertion point filled.
577
+ func insertionPointRects( at index: Int ) -> [ NSRect ] {
578
+
579
+ guard let layoutManager = self . layoutManager else { assertionFailure ( ) ; return [ ] }
580
+
581
+ let scale = self . scale
582
+ return layoutManager. insertionPointRects ( at: index)
583
+ . map { $0. offset ( by: self . textContainerOrigin) }
584
+ . map { rect in
585
+ NSRect ( x: ( rect. minX * scale) . rounded ( . down) / scale,
586
+ y: rect. minY,
587
+ width: 1 / scale,
588
+ height: rect. height)
589
+ }
590
+ }
591
+
592
+
593
+ /// Enables insertion point blink timer to draw insertion points forcibly.
594
+ private func enableOwnInsertionPointTimer( ) {
595
+
596
+ guard self . insertionPointTimer? . isCancelled ?? true else { return }
597
+
598
+ let period = UserDefaults . standard. textInsertionPointBlinkPeriod
599
+
600
+ let timer = DispatchSource . makeTimerSource ( queue: . main)
601
+ timer. schedule ( deadline: . now( ) )
602
+ timer. setEventHandler { [ unowned self] in
603
+ self . insertionPointOn. toggle ( )
604
+ let interval = self . insertionPointOn ? period. on : period. off
605
+ timer. schedule ( deadline: . now( ) + . milliseconds( interval) )
606
+ self . setNeedsDisplay ( self . visibleRect, avoidAdditionalLayout: true )
607
+ }
608
+ timer. resume ( )
609
+
610
+ self . insertionPointTimer? . cancel ( )
611
+ self . insertionPointTimer = timer
612
+ }
613
+ }
614
+
615
+
616
+ @available ( macOS, deprecated: 14 )
617
+ private struct BlinkPeriod {
618
+
619
+ var on : Int
620
+ var off : Int
621
+ }
622
+
623
+
624
+ @available ( macOS, deprecated: 14 )
625
+ private extension UserDefaults {
626
+
627
+ var textInsertionPointBlinkPeriod : BlinkPeriod {
628
+
629
+ let onPeriod = self . integer ( forKey: " NSTextInsertionPointBlinkPeriodOn " )
630
+ let offPeriod = self . integer ( forKey: " NSTextInsertionPointBlinkPeriodOff " )
631
+
632
+ return BlinkPeriod ( on: ( onPeriod > 0 ) ? onPeriod : 500 ,
633
+ off: ( offPeriod > 0 ) ? offPeriod : 500 )
634
+ }
635
+ }
0 commit comments