-
-
Notifications
You must be signed in to change notification settings - Fork 19
Hold overlay position across post-accept AX reconciles #349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+281
−6
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import CoreGraphics | ||
| import Foundation | ||
|
|
||
| /// File overview: | ||
| /// Pure decision for whether a reconcile tick should reposition the visible ghost-text overlay. | ||
| /// | ||
| /// Why this file exists: | ||
| /// `SuggestionCoordinator` reconciles the active suggestion many times: on every focus poll, on | ||
| /// every settings publication, and on the +30ms post-insertion refresh that fires after each Tab | ||
| /// accept. The post-insertion path is the one that visibly hurts: AX commonly returns a slightly | ||
| /// drifted `caretRect` / `observedCharWidth` after a synthesized insertion, and re-rendering | ||
| /// against those drifted measurements is what causes the visible one-frame "shift left and down | ||
| /// then snap back" the user sees on accept. The gate below holds the existing geometry whenever | ||
| /// the field, text, and on-screen field bounds have not materially moved; legitimate context | ||
| /// changes (field switch, window drag, text change) still re-anchor. Keeping the rule outside the | ||
| /// coordinator means it can be unit-tested in isolation from any AppKit state. | ||
| enum SuggestionOverlayStabilityGate { | ||
| /// Slack absorbed when comparing `inputFrameRect` between renders. 1pt is enough to swallow | ||
| /// the sub-pixel noise that mixed Retina/non-Retina setups produce on consecutive AX reads | ||
| /// of the same field, while still catching whole-pixel movements from a real window drag. | ||
| private static let inputFrameTolerance: CGFloat = 1 | ||
|
|
||
| /// Returns `true` when the coordinator should call `presentOverlay` for this reconcile tick. | ||
| /// Returns `false` to hold the existing overlay geometry exactly as it was last drawn. | ||
| /// | ||
| /// Re-anchor when: | ||
| /// - The overlay is currently hidden (this is a fresh show). | ||
| /// - The focus session changed (different field, or the same field after focus toggled). | ||
| /// - The displayed text changed (user partially accepted, or typed-through advanced the tail). | ||
| /// - The host editor's frame moved on screen (window drag, sheet appear, etc.). | ||
| static func shouldRePresent( | ||
| currentOverlay: OverlayState, | ||
| newText: String, | ||
| newInputFrameRect: CGRect?, | ||
| newFocusChangeSequence: UInt64 | ||
| ) -> Bool { | ||
| // Render mode is the third associated value; it is not part of the stability decision, so | ||
| // we ignore it. A mode change still re-anchors because text or geometry will also differ. | ||
| guard case let .visible(currentText, currentGeometry, _) = currentOverlay else { | ||
| return true | ||
| } | ||
| if currentGeometry.focusChangeSequence != newFocusChangeSequence { | ||
| return true | ||
| } | ||
| if currentText != newText { | ||
| return true | ||
| } | ||
| // `observedCharWidth` is intentionally NOT compared here. Drift in that value also affects | ||
| // `GhostSuggestionLayout.singleLineFits` (and therefore the panel-origin branch), so during | ||
| // a sustained window drag where `inputFrameRect` also moves, the first re-anchor past the | ||
| // tolerance can render with a drifted char-width for one frame. Including char-width in the | ||
| // gate would re-introduce the post-accept jitter this file exists to suppress, so we accept | ||
| // the drag-time tradeoff. If a future host shows the wrong-layout frame in practice, the fix | ||
| // belongs in `GhostSuggestionLayout` (smoothing char-width) rather than this gate. | ||
| switch (currentGeometry.inputFrameRect, newInputFrameRect) { | ||
| case (nil, nil): | ||
| return false | ||
| case (nil, _), (_, nil): | ||
| return true | ||
| case let (old?, new?): | ||
| return abs(old.origin.x - new.origin.x) > inputFrameTolerance | ||
| || abs(old.origin.y - new.origin.y) > inputFrameTolerance | ||
| || abs(old.size.width - new.size.width) > inputFrameTolerance | ||
| || abs(old.size.height - new.size.height) > inputFrameTolerance | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.