Skip to content

feat(ui/dock): click-to-activate on terminal body — design needed (re-attempt) #5

@psmon

Description

@psmon

Background

Original UX gap: in a split-pane layout, the user has to click the
tab strip at the top of a pane to make that Document active.
Clicking the terminal body itself does nothing — the win32 console
control consumes the mouse event before WPF sees it.

What was tried (commit 6143c60 → reverted in )

terminal.GotFocus += (_, _) => { if (!tab.Document.IsActive) tab.Document.IsActive = true; }

Why it failed

Setting IsActive from inside GotFocus puts the AvalonDock active-
document state into a feedback loop:

  1. User clicks terminal A → win32 child takes keyboard focus
  2. Routed GotFocus bubbles to the WPF tree
  3. Handler sets tab.Document.IsActive = true for A
  4. AvalonDock pushes focus around to enforce the new active state,
    which moves keyboard focus and fires GotFocus on terminal B
  5. B's handler sets B's Document active
  6. Step 4 again, but in the other direction

Effective result: the active highlight ricochets between panes at
win32 input speeds and no input lands anywhere. Severe UX freeze
reported within seconds of the build going live.

The !IsActive guard didn't break the loop because each cycle
does legitimately change IsActive — the guard only stops a true
no-op rebind, not the ping-pong.

Candidates for a future re-attempt

  • One-shot Win32 hook on WM_LBUTTONDOWN in EasyTerminalControl's
    HWND. Fires exactly once per click, no focus-loop entry. Requires
    picking up the underlying HWND after ConPTYTerm is bound and
    unsubscribing on tab close.
  • Dispatcher.BeginInvoke debounce so the IsActive write happens
    after the focus-traffic settles. May still loop if AvalonDock's
    focus push is itself dispatched.
  • AvalonDock dockManager.Layout.RootDocument / ActivateDocument
    may have a path that doesn't drive focus. Needs library reading.
  • Track who initiated the focus (mouse vs programmatic) so the
    handler only acts on user gestures.

Each candidate needs an empirical test in a 2-pane layout before
landing.

Acceptance test

  • Build a 2-pane split with terminal A on the left, terminal B on the right
  • Click terminal A's body (not its tab strip) → A's pane becomes active, no flicker
  • Type into A → keystrokes land in A
  • Click terminal B's body → B's pane becomes active, no flicker
  • Type into B → keystrokes land in B
  • Repeat the click-and-type cycle 10×; no observable lag, no active-highlight ricochet

Closes-when

  • One of the candidate signals lands without the feedback loop
  • Acceptance test above passes
  • No regression on single-pane layouts (focus still works as today)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions