Skip to content

Commit 4c8268c

Browse files
authored
fix(windows): stop the grid cells reappearing behind an open subgrid (#1622)
1 parent 19b40a3 commit 4c8268c

3 files changed

Lines changed: 328 additions & 39 deletions

File tree

docs/CROSS_PLATFORM.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,7 @@ discovery rather than the mode itself.
782782
| **Hints** | Label arrow / tail | ✅ NSBezierPath | ✅ Cairo triangle | ✅ sampled triangle, see below |
783783
| **Hints** | Label placement | ✅ top / center / bottom | ✅ top / center / bottom | ✅ top / center / bottom |
784784
| **Grid** | Virtual pointer indicator ||||
785-
| **Grid** | What an open subgrid shows | ✅ the subgrid alone | ✅ the subgrid alone | ⚠️ the parent cells return under it on the next repaint |
785+
| **Grid** | What an open subgrid shows | ✅ the subgrid alone | ✅ the subgrid alone | the subgrid alone |
786786
| **Recursive grid**| Transition animation ||||
787787
| **Recursive grid**| Virtual pointer indicator ||||
788788
| **Recursive grid**| Sub-key preview | ✅ mini-grid of next keys | ✅ mini-grid of next keys | ✅ mini-grid of next keys |

internal/adapter/overlay/windows/overlay.go

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"unsafe"
1111

1212
"go.uber.org/zap"
13+
"golang.org/x/sys/windows"
1314

1415
"github.com/y3owk1n/neru/internal/adapter/overlay/render/badge"
1516
gridcomponent "github.com/y3owk1n/neru/internal/adapter/overlay/render/grid"
@@ -26,8 +27,40 @@ const (
2627
winSubgridFontScale = 0.7
2728
)
2829

30+
// overlayWindow is the surface the grid overlay paints on: what winOverlay
31+
// asks of a winplatform.OverlayWindow, and nothing it does not. The concrete
32+
// window is the one implementation that ships; the tests in this package
33+
// stand a recording one in for it, because reaching the drawing code through
34+
// the real type means creating a layered HWND on an interactive desktop.
35+
type overlayWindow interface {
36+
HWND() windows.HWND
37+
Healthy() bool
38+
Visible() bool
39+
Bounds() image.Rectangle
40+
Backend() string
41+
Show()
42+
Hide()
43+
Clear()
44+
ResizeToActiveScreen() error
45+
Destroy()
46+
FillRect(bounds image.Rectangle, color uint32)
47+
StrokeRect(bounds image.Rectangle, color uint32, lineWidth float64)
48+
FillRoundedRect(bounds image.Rectangle, radius float64, color uint32)
49+
StrokeRoundedRect(bounds image.Rectangle, radius float64, color uint32, lineWidth float64)
50+
FillTriangle(vertexA, vertexB, vertexC image.Point, color uint32)
51+
DrawTextCentered(
52+
text string,
53+
bounds image.Rectangle,
54+
fontFamily string,
55+
fontSize float64,
56+
color uint32,
57+
)
58+
DrawPointerGlyph(center image.Point, size int, char string, fontFamily string, color uint32)
59+
Flush() error
60+
}
61+
2962
type winOverlay struct {
30-
window *winplatform.OverlayWindow
63+
window overlayWindow
3164
logger *zap.Logger
3265
// renderMu is the manager's lock, which every draw here runs under; the
3366
// transition goroutine takes it per frame (transition.go).
@@ -275,7 +308,7 @@ func (o *winOverlay) ShowSubgrid(
275308

276309
o.currentSubgrid = cell
277310
o.gridPointer = pointer
278-
o.repaintSubgrid()
311+
o.redrawGrid()
279312
}
280313

281314
// SetGridPointer records where grid mode's pointer stand-in belongs and
@@ -288,11 +321,6 @@ func (o *winOverlay) ShowSubgrid(
288321
// repainted. The record is kept even when nothing can be painted yet, for the
289322
// same reason UpdateGridMatches keeps its prefix while the window is hidden:
290323
// the next Show reads it.
291-
//
292-
// With a subgrid open, the repaint is the subgrid's own: a full grid redraw
293-
// here would put the parent cells back under it, which is the repaint
294-
// docs/CROSS_PLATFORM.md already reports for a narrowing keystroke and one
295-
// this call has no reason to add to.
296324
func (o *winOverlay) SetGridPointer(pointer recursivegridcomponent.VirtualPointerState) {
297325
if o == nil || pointer == o.gridPointer {
298326
return
@@ -304,12 +332,6 @@ func (o *winOverlay) SetGridPointer(pointer recursivegridcomponent.VirtualPointe
304332
return
305333
}
306334

307-
if o.currentSubgrid != nil {
308-
o.repaintSubgrid()
309-
310-
return
311-
}
312-
313335
o.redrawGrid()
314336
}
315337

@@ -420,6 +442,21 @@ func (o *winOverlay) backendName() string {
420442
return o.window.Backend()
421443
}
422444

445+
// redrawGrid paints the grid surface as it currently stands, which is either
446+
// the subgrid one cell was opened into or the cells themselves, with the
447+
// pointer stand-in on whichever it is. It is the only answer to "what does
448+
// this surface show", so the open, a narrowing keystroke, a pointer move and
449+
// the hide-unmatched toggle's next repaint all arrive at the same screen.
450+
//
451+
// The subgrid is the whole surface while one is open (#1491, #1610). macOS
452+
// settles that: its ShowSubgrid (adapter/overlay/render/grid/overlay_darwin.go)
453+
// clears the overlay window and hands NeruDrawGridCells the nine subgrid cells,
454+
// and that bridge replaces the view's cell array rather than adding to it, so
455+
// the parent cells leave the reference platform's overlay for as long as the
456+
// subgrid is up. Linux agrees since #1491. The guard lives here rather than at
457+
// each caller because every repaint reaches this: a narrowing keystroke that
458+
// put the parent cells back under the subgrid changed what was on screen
459+
// without the user asking for it.
423460
func (o *winOverlay) redrawGrid() {
424461
o.redrawGridWithoutFlush()
425462
o.flushOverlay("grid")
@@ -448,6 +485,27 @@ func (o *winOverlay) redrawGridWithoutFlush() {
448485

449486
o.Clear()
450487

488+
if o.currentSubgrid != nil {
489+
o.drawSubgrid(o.currentSubgrid.Bounds(), o.cachedStyle)
490+
} else {
491+
o.drawGridCells()
492+
}
493+
494+
o.drawGridPointer(o.gridPointer)
495+
496+
if o.logger != nil {
497+
o.logger.Debug(
498+
"redraw complete",
499+
zap.Int("cells", len(o.cachedGrid.AllCells())),
500+
zap.Bool("subgrid", o.currentSubgrid != nil),
501+
zap.Bool("healthy", o.window.Healthy()),
502+
)
503+
}
504+
}
505+
506+
// drawGridCells paints every cell of the cached grid, marked against the
507+
// prefix typed so far and thinned by hide-unmatched.
508+
func (o *winOverlay) drawGridCells() {
451509
style := o.cachedStyle
452510
prefix := o.currentPrefix
453511

@@ -482,20 +540,6 @@ func (o *winOverlay) redrawGridWithoutFlush() {
482540
)
483541
}
484542
}
485-
486-
if o.currentSubgrid != nil {
487-
o.drawSubgrid(o.currentSubgrid.Bounds(), style)
488-
}
489-
490-
o.drawGridPointer(o.gridPointer)
491-
492-
if o.logger != nil {
493-
o.logger.Debug(
494-
"redraw complete",
495-
zap.Int("cells", len(o.cachedGrid.AllCells())),
496-
zap.Bool("healthy", o.window.Healthy()),
497-
)
498-
}
499543
}
500544

501545
func (o *winOverlay) flushOverlay(context string) {
@@ -517,17 +561,10 @@ func (o *winOverlay) flushOverlay(context string) {
517561
}
518562
}
519563

520-
// repaintSubgrid paints the open subgrid and the pointer on it as the whole
521-
// surface (#1491). The keys it draws with were handed over by the manager
522-
// when the subgrid was opened, and the surface has not been rebuilt since:
523-
// only a manager draw rebuilds it, and every one of those syncs the keys.
524-
func (o *winOverlay) repaintSubgrid() {
525-
o.Clear()
526-
o.drawSubgrid(o.currentSubgrid.Bounds(), o.cachedStyle)
527-
o.drawGridPointer(o.gridPointer)
528-
o.flushOverlay("subgrid")
529-
}
530-
564+
// drawSubgrid paints the finer grid inside one cell. The keys it draws with
565+
// were handed over by the manager when the subgrid was opened, and the
566+
// surface has not been rebuilt since: only a manager draw rebuilds it, and
567+
// every one of those syncs the keys.
531568
func (o *winOverlay) drawSubgrid(bounds image.Rectangle, style gridcomponent.Style) {
532569
// The keys the subgrid is drawn with, which are the keys the mode layer
533570
// selects on (internal/domain/grid/subgrid_keys.go).

0 commit comments

Comments
 (0)