Skip to content

Window: replace input/render events with a single AppWindowHandler#39

Open
codymullins wants to merge 2 commits into
mainfrom
feat/window-handler
Open

Window: replace input/render events with a single AppWindowHandler#39
codymullins wants to merge 2 commits into
mainfrom
feat/window-handler

Conversation

@codymullins

Copy link
Copy Markdown
Contributor

Why

A window has exactly one owner, so event Action<T> was the wrong tool for its input/render/resize/focus callbacks: it advertises multicast and arbitrary subscribe/unsubscribe ordering that a single-owner window never wants. This moves those callbacks to a single handler object.

(Decision recap: synchronous handler, not async ValueTask — these callbacks fire on the main-thread pump and per-window render thread, and the input/render path is hot, so async there would block the pump or churn state machines on the hottest path. ValueTask<T> stays in reserve for a future decision callback, of which there are none today.)

What changed

  • AppWindowHandler (new) — abstract class with virtual no-op OnRenderFrame / OnResized / OnPointerInput / OnKeyInput / OnTextInput / OnFocusChanged. Subclass it.
  • CallbackAppWindowHandler (new) — a sealed adapter whose callbacks are plain single delegates (not multicast), for lambda-style hookup in samples and quick code.
  • WindowFocusEvent (new) — payload record struct for focus.
  • AppWindow — the six public events become one public AppWindowHandler? Handler { get; set; }.
  • FrameLoop — is the window's handler while attached. It drives the frame and forwards input, focus, and post-reconfigure resize to an app handler set on FrameLoop.Handler. Apps set loop.Handler instead of the window's, and keep drawing through OnRender.
  • Samples + the windowed harness migrated.

Deliberately left as events

  • AppHost.WindowClosed — a genuine broadcast lifecycle notification; independent listeners (the app, a resource manager, logging) legitimately observe each window's close. event is the right tool here.
  • The internal IWindowBackend seam — single internal subscriber (AppWindow) and an implementation detail; converting it is churn across both backends with no public-API benefit. Possible follow-up.

Not included (approved shape vs. shipped)

OnCloseRequested appeared in the approved sketch but is omitted: there is no close-request/veto plumbing today (close is fire-and-forget via RequestClose), so shipping it would be uninvoked public API. It is the natural home for a close veto when that lands.

Verification

  • dotnet build Skyline.slnx clean, 0 warnings (incl. Skyline.Apple on the macOS workload).
  • All 8 edited desktop samples build.
  • Skyline.Tests — 62 passed. Windowed harness — 94 checks passed.
  • tools/format-check.sh clean.

Sequencing

Landed after #36 (focus) and #37 (pointer enter/leave) merged, then migrated the whole surface in one sweep, so no new event Action<T> was added in the old style first.

… handler

A window has exactly one owner, so its input, render, resize, and focus
callbacks move from multicast `event Action<T>` to a single
`AppWindow.Handler` (an `AppWindowHandler`). Subclass it, or use the
`CallbackAppWindowHandler` adapter for lambda-style hookup.

FrameLoop is the window's handler while attached: it drives the frame and
forwards input, focus, and post-reconfigure resize to an app handler set on
`FrameLoop.Handler`, so apps set that instead of the window's.

AppHost.WindowClosed stays an event — it is a true broadcast notification
where independent listeners legitimately observe each window's close. The
internal IWindowBackend seam stays events too: a single internal subscriber
and an implementation detail.

Samples and the windowed harness move to the handler.
Copilot AI review requested due to automatic review settings June 18, 2026 20:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…t directly

FrameLoop.Handler is now a get-only CallbackAppWindowHandler the loop owns,
so callers set callbacks on it (loop.Handler.KeyInput = ...) instead of
newing their own handler. Drops the per-caller construction in the samples
and the windowed harness.
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.

2 participants