Skip to content

eframe: Add App::transform_primitives and App::post_platform_output hooks#8138

Open
Le-Syl21 wants to merge 1 commit into
emilk:mainfrom
Le-Syl21:eframe-app-hooks
Open

eframe: Add App::transform_primitives and App::post_platform_output hooks#8138
Le-Syl21 wants to merge 1 commit into
emilk:mainfrom
Le-Syl21:eframe-app-hooks

Conversation

@Le-Syl21
Copy link
Copy Markdown

Summary

Adds two optional methods on the App trait that give integrations a place to inspect or transform the per-frame egui pipeline output before it reaches the platform.

fn transform_primitives(
    &mut self,
    ctx: &egui::Context,
    primitives: &mut Vec<egui::epaint::ClippedPrimitive>,
) {}

fn post_platform_output(
    &mut self,
    ctx: &egui::Context,
    platform_output: &mut egui::PlatformOutput,
) {}

Both default to no-op, so existing apps are unaffected.

Where they fire

Hook Fires after Fires before
transform_primitives Context::tessellate painter paint_and_update_textures
post_platform_output Context::run_ui returns egui_winit.handle_platform_output consumes it

Wired into both glow_integration and wgpu_integration. Web (wasm32) is not touched in this PR — could be a follow-up if there's interest.

Use cases

These are general-purpose hooks, not tied to any specific feature. Examples that motivated them:

  • Viewport-level transforms: rotation (e.g. for a 90°-mounted kiosk display), mirroring, custom projections — applied uniformly to the whole frame without touching individual widgets. The shipping companion crate egui-rotate uses these hooks to integrate cleanly with eframe; without them it can only target custom winit/glow integrations.
  • Debugging / instrumentation: dump primitive counts, log overdraw, inject debug overlays after the UI has been laid out.
  • Custom render passes: anything that needs access to the final primitive list (e.g. injecting a post-processing pass on a subset of layers).
  • Software cursor capture: read PlatformOutput::cursor_icon to drive a custom cursor (where the OS cursor is hidden, e.g. cabinet displays where the OS cursor cannot be rotated).
  • Output interception: clipboard / IME / cursor-icon overrides for kiosk and embedded scenarios.

Why not a single hook?

The two hooks fire at different points in the pipeline (tessellate→paint vs. run_ui→handle_platform_output) and target different data (primitives vs. platform output). Coalescing them would either add a hook that fires earlier than needed for one of the use cases, or split the work into one hook with two unrelated parameters.

Why not extend Context instead?

These are integration-time concerns (post-tessellation, pre-paint; post-run, pre-OS-dispatch). They don't fit the egui Context API, which is mid-pass. The App trait is the natural place: it's already where raw_input_hook lives for the symmetric input side.

Test plan

  • cargo build -p eframe clean
  • cargo clippy -p eframe --all-features -- -D warnings clean
  • cargo fmt -p eframe --check clean
  • cargo test -p eframe --lib passes
  • Manual: drive a vendored egui-rotate integration through these hooks on a real eframe app, verify rotation + cursor work — done locally on my pinball cabinet launcher (PinReady), can share details if useful

Background

This is a smaller, more general follow-up to #8113, which tried to integrate viewport rotation directly into egui+eframe and was declined as too niche / too much surface to maintain. That feedback was fair. The follow-up published the rotation logic as a standalone crate (egui-rotate) which works fine with custom integrations (winit + glow / wgpu / SDL3 directly). However it cannot integrate with eframe because eframe owns the tessellate→paint pipeline with no hook in between.

These two hooks fix that — and stay generic. They're not specific to rotation; the doc-comments describe the broader use cases.

If preferred, I'm happy to:

  • rename either method (pre_paint / pre_handle_output?)
  • gate them behind a feature flag
  • only add transform_primitives and drop post_platform_output (the cursor icon use case can be worked around with a fixed icon)
  • wire them into the wasm integration too

🤖 Drafted with Claude Code

@github-actions
Copy link
Copy Markdown

Preview is being built...

Preview will be available at https://egui-pr-preview.github.io/pr/8138-eframe-app-hooks

View snapshot changes at kitdiff

…t` hooks

Two new optional methods on the `App` trait give integrations a place to
inspect or transform the per-frame egui pipeline output before it reaches
the platform.

- `transform_primitives(&mut self, ctx, &mut Vec<ClippedPrimitive>)` runs
  after `Context::tessellate` and before the painter (glow / wgpu) submits
  draw calls. The primitives can be rewritten, replaced, or stripped.

- `post_platform_output(&mut self, ctx, &mut PlatformOutput)` runs after
  `Context::run_ui` and before the integration consumes platform output
  (cursor icon dispatch, IME state, clipboard writes, accessibility
  updates). The output can be inspected or mutated in place.

Both hooks default to no-op, so existing apps are unaffected.

Use cases:
- viewport-level transforms applied uniformly to the whole frame (rotation,
  mirroring, custom projections) without touching individual widgets
- debugging / instrumentation: dump primitive counts, log overdraw, inject
  debug overlays
- custom render passes that need access to the final primitive list
- capturing the cursor icon for a custom software cursor
- intercepting clipboard writes / cursor commands for kiosk or embedded
  scenarios

The hooks are wired into both the glow and wgpu native integrations.
Web (wasm32) is not currently affected — the hooks could be wired in a
follow-up if there is demand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le-Syl21 added a commit to Le-Syl21/PinReady that referenced this pull request Apr 30, 2026
The full-rotation egui fork (~3000 lines diff) is replaced by:
  - the standalone `egui-rotate` crate (rotation logic + software cursor)
  - a slim Le-Syl21/egui fork on `pinready-deps` (~150 lines diff total):
      * eframe::App::transform_primitives + post_platform_output hooks
        (pending upstream as PR emilk/egui#8138)
      * ViewportBuilder::with_monitor (no PR yet)
      * Key::ShiftLeft/Right + IntlBackslash physical key variants
        (pending upstream as PR emilk/egui#8127)

App-side changes:
  - new fields: `rotation`, `cursor: SoftwareCursor`, `last_cursor_icon`
  - `set_rotation()` setter called once at construction from main
  - `enable_kiosk_cursor` now also configures `cursor.set_scale(3.0)`
    + `cursor.set_lock(true)`
  - implement `raw_input_hook` → cursor.process_input (rotates input,
    captures cursor)
  - implement `transform_primitives` → egui_rotate::transform_clipped_primitives
    (rotates output back to physical screen space)
  - implement `post_platform_output` → captures cursor icon for next
    frame's draw, suppresses OS cursor when captured
  - draw the software cursor at the top of `fn ui` on a Foreground layer

Secondary viewports (BG / DMD / Topper) drop their `with_rotation(None)`
and `set_viewport_rotation(None)` — the rotation only applies to the
root, gated by `ctx.viewport_id() != ROOT` in every hook.

Build, clippy clean. Visual validation pending on the cabinet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le-Syl21 added a commit to Le-Syl21/PinReady that referenced this pull request Apr 30, 2026
… labels

Architectural refactor of the egui dependency surface.

The previous full-rotation egui fork (~3000 lines diff) is split into:
  - The standalone `egui-rotate` crate (rotation logic + software cursor),
    published on crates.io: https://crates.io/crates/egui-rotate
  - A slim Le-Syl21/egui fork on `pinready-deps` (~150 lines diff total),
    pending three independent upstream PRs:
      * eframe::App::transform_primitives + post_platform_output hooks
        (emilk/egui#8138)
      * ViewportBuilder::with_monitor / ViewportCommand::SetMonitor
        (emilk/egui#8140)
      * Key::ShiftLeft/Right + IntlBackslash physical key variants
        (emilk/egui#8127)
  - Localized SDL key labels via the `sdl-keybridge` crate, replacing
    PinReady's hand-curated `key_*` rust-i18n entries.

User-visible changes:
  - Wizard input page: key labels now localized in the user's UI language
    via sdl-keybridge (e.g. "Alt Gauche" instead of "Left Alt" in French).
  - Cabinet mode: cursor I-beam in TextEdits now renders perpendicular to
    rotated text (was parallel before — fixed in egui-rotate 0.1.2).
  - No behavior change for desktop / wizard mode.

App-side changes (internal):
  - new fields on `App`: `rotation`, `cursor: SoftwareCursor`,
    `last_cursor_icon`
  - `set_rotation()` setter called from `main` at construction
  - `enable_kiosk_cursor()` now configures `cursor.set_scale(3.0)` +
    `cursor.set_lock(true)`
  - implements `eframe::App::raw_input_hook` (input rotation + cursor capture),
    `transform_primitives` (output rotation, ROOT viewport only via
    explicit viewport_id parameter), and `post_platform_output` (cursor
    icon capture + suppression)
  - software cursor drawn at the top of `fn ui` on a Foreground layer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@emilk emilk left a comment

Choose a reason for hiding this comment

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

Did you try using the egui::Plugin system to achieve this? In particular: Plugin::output_hook

Comment on lines +10 to +15
## Unreleased

### ⭐ Added
* Add `App::transform_primitives` and `App::post_platform_output` hooks to inspect/transform tessellated primitives and platform output before they are dispatched.


Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

revert: we generate the changelog on release

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