diff --git a/internal/test/e2e/find_test.go b/internal/test/e2e/find_test.go index 29981d4..3045059 100644 --- a/internal/test/e2e/find_test.go +++ b/internal/test/e2e/find_test.go @@ -113,6 +113,10 @@ func TestRealFile_FindBarAndNavigation(t *testing.T) { if got := reg.(ui.Region); got.Y != ui.Dp(32)+editor.FindBarHeight { t.Fatalf("editor region top %d, want %d (find bar must not cover text)", int(got.Y), int(ui.Dp(32)+editor.FindBarHeight)) } + // The editor field reports UNFOCUSED while the find bar is open (the + // renderer hides its caret for unfocused fields; main hands key focus to + // the search input). + waitForEditorField(t, h, func(tf ui.TextField) bool { return !tf.Focused }) // Type the query through the same channel main uses. h.Logic().FindQueryChan() <- "NEEDLE" // case-insensitive @@ -190,6 +194,8 @@ func TestRealFile_FindBarAndNavigation(t *testing.T) { if focus.(string) != "editor_text" { t.Fatalf("focus %q, want editor_text after close", focus) } + // And the editor field reports focused again (caret returns). + waitForEditorField(t, h, func(tf ui.TextField) bool { return tf.Focused }) } func TestRealFile_FindMatchesSurviveEdits(t *testing.T) { diff --git a/internal/ui/element.go b/internal/ui/element.go index ad8e30b..f758189 100644 --- a/internal/ui/element.go +++ b/internal/ui/element.go @@ -337,7 +337,7 @@ func (tf TextField) Draw(gtx layout.Context, r *Renderer) { r.lastSelCaret = -1 r.lastIMEShowSeq = 0 } - r.drawWrappedText(gtx, tf.Value, tf.region, tf.WordWrap, tf.WrapWidth, tf.ScrollOffset, tf.CursorPosition, tf.SelectionStart, tf.SelectionEnd, tf.CaretDrag, tf.MatchRanges, tf.CurrentMatch) + r.drawWrappedText(gtx, tf.Value, tf.region, tf.WordWrap, tf.WrapWidth, tf.ScrollOffset, tf.CursorPosition, tf.SelectionStart, tf.SelectionEnd, tf.CaretDrag, tf.MatchRanges, tf.CurrentMatch, tf.Focused) } // runeCount returns the number of UTF-8 runes in s[:bytePos] (bytePos is a diff --git a/internal/ui/render.go b/internal/ui/render.go index 7d677ce..5aeba04 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -777,7 +777,7 @@ func (r *Renderer) drawRangeHighlight(gtx layout.Context, layout *GlyphLayout, s } } -func (r *Renderer) drawWrappedText(gtx layout.Context, str string, reg Region, wordWrap bool, wrapWidth Dp, scrollOffset Dp, cursorPos, selStart, selEnd int, caretDrag bool, matchRanges [][2]int, currentMatch int) { +func (r *Renderer) drawWrappedText(gtx layout.Context, str string, reg Region, wordWrap bool, wrapWidth Dp, scrollOffset Dp, cursorPos, selStart, selEnd int, caretDrag bool, matchRanges [][2]int, currentMatch int, focused bool) { if str == "" { return } @@ -920,14 +920,18 @@ func (r *Renderer) drawWrappedText(gtx layout.Context, str string, reg Region, w return CaretPoint(layout, str, byteOff, ascent, lineH) } - // Draw the caret only when the cursor's byte is inside the shaped window - // (window-relative: [0, len(str)]). The window covers the viewport - // exactly, so an out-of-range cursor is off-screen and its caret must not - // be drawn; the caller used to clamp it to 0, which made the caret jump - // onto the top (or, past the end, the bottom) visible line whenever the - // user scrolled past it. The boundary values are on-screen: 0 is the - // window's first byte and len(str) is the window's last insertion point. - if cursorPos >= 0 && cursorPos <= len(str) { + // Draw the caret only when (a) the field holds key focus and (b) the + // cursor's byte is inside the shaped window (window-relative: + // [0, len(str)]). (a): while another input is focused — the find bar's + // search input is the case that motivated this — a live caret in the + // editor reads as if the editor still had focus; the caret returns when + // focus comes back. (b): the window covers the viewport exactly, so an + // out-of-range cursor is off-screen and its caret must not be drawn; the + // caller used to clamp it to 0, which made the caret jump onto the top + // (or, past the end, the bottom) visible line whenever the user scrolled + // past it. The boundary values are on-screen: 0 is the window's first + // byte and len(str) is the window's last insertion point. + if focused && cursorPos >= 0 && cursorPos <= len(str) { // Determine cursor position from `layout` and `cursorPos` cursorX, cursorY := caretPoint(cursorPos) cursorX = reg.X + cursorX