Skip to content

fix: always complete mouse presses and handle in-progress selections on render - #2951

Merged
Danielku15 merged 1 commit into
developfrom
fix/2949-mouse-press-lifecycle
Oct 11, 2026
Merged

Danielku15 merged 1 commit into
developfrom
fix/2949-mouse-press-lifecycle

Conversation

@Danielku15

Copy link
Copy Markdown
Member

Note

AI-authored disclosure (alphatab-ai-authored-v1)

Portions of this content were authored by an AI agent. The agent has read
AGENTS.md and the human submitter accepts responsibility for
compliance with the rules in that document.

Issues

Fixes #2949

Proposed changes

The TypeError in #2949 is a symptom. Two separate defects cause it, so guarding _selectionEnd at the crash site is not enough.

1. Platform layer: a press could start and never end.
No IContainer implementation guaranteed that a press started on the control ends with a mouseUp:

  • Web: mouseup was only observed on the element itself, so a release outside the surface was lost.
  • WPF: the mouse was never captured.
  • WinForms: a lost capture (alt-tab, modal dialog) ended the press without a MouseUp.
  • Android: ACTION_CANCEL was ignored.

The API then kept _isBeatMouseDown = true indefinitely. Besides the crash, this disabled auto-scroll during playback (shouldScroll && !this._isBeatMouseDown) and made plain hovering over beats extend the selection.

IContainer.mouseUp/mouseMove now document the contract: every press started on the control ends with exactly one mouseUp, and moves during an active press are reported even outside the control. Each platform implements it:

  • Web (HtmlElementContainer):
    • While the left button is held, mousemove/mouseup are observed on the owner document.
    • A move with (buttons & 1) === 0 ends a press whose release was never observed, e.g. one released outside the window.
    • Events are not delivered twice to element and document listeners.
    • Hover moves without a press behave as before.
  • WPF: the control captures the mouse on left press, releases it on MouseUp, and LostMouseCapture during a press raises MouseUp.
  • WinForms: MouseCaptureChanged during a press raises MouseUp. This is deferred, so a regular release does not report twice.
  • Android: ACTION_CANCEL is handled like ACTION_UP, and actionMasked is used.

Native listeners are attached on first subscription. off on the mouse emitters now works; before, it did nothing.

2. Core: the selection was redrawn twice after a render, and one of the two paths was wrong.

A start without an end is a legitimate state: the button is still held on one beat, or the user clicked with resetPlaybackRangeOnClick = false. That last case crashed on the next render even with perfect mouse handling.

Both paths are replaced by one _refreshSelectionHighlight() that:

  • runs after every render,
  • drops the cached bounds,
  • handles a missing end (empty highlight).

Highlights set through highlightPlaybackRange without range selection are still redrawn after a re-render (#2646).

In addition, a new press resets the beat and note press state left over from the previous one. The mouseUp path clears it in a finally, so a throwing beatMouseUp listener can no longer leave the press active.

Checklist

  • I consent that this change becomes part of alphaTab under its current or any future open source license
  • This PR is linked to an accepted issue (see above)
  • Changes are implemented
  • New tests were added
  • I have read AGENTS.md if an AI helped draft any part of this PR

New tests are in test/AlphaTabApiMouse.test.ts: render during a held press, render after a click without range reset, rebuilt bounds after re-render, API highlight without range selection, and a release outside beats ending the press. They pass on TypeScript, C# and Kotlin. Without the core change, the first three fail with the TypeError from the issue.

AI authorship disclosure

  • No AI agent authored any part of this PR (description, code, tests, or commit messages)
  • An AI agent contributed to this PR. The AI-authored disclosure block
    (alphatab-ai-authored-v1) is present at the top of this body, and I have
    personally reviewed every change and can explain each one

Further details

  • This is a breaking change
  • This change will require update of the documentation/website

@Danielku15 Danielku15 self-assigned this Oct 11, 2026
@Danielku15
Danielku15 merged commit aa824f2 into develop Oct 11, 2026
6 checks passed
@Danielku15
Danielku15 deleted the fix/2949-mouse-press-lifecycle branch October 11, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Uncaught TypeError when a beat press was released outside the alphaTab surface

1 participant