feat(frontend): live host-input capture for Mouse/Super Scope#114
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements live host-input capture for the SNES Mouse and Super Scope peripherals in the rustysnes-frontend crate, utilizing egui's pointer state and reusing the existing letterbox scale calculation from Gfx to map absolute coordinates. It also corrects documentation in docs/frontend.md regarding the currently unimplemented gamepad support. The review feedback correctly identifies that relative mouse movement deltas are not scaled to the emulated SNES screen space, causing mouse sensitivity to vary with the host window size and resolution, and suggests scaling these deltas using the active letterbox scale factor.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
Adds live host-pointer input capture for port-2 Mouse and Super Scope peripherals in rustysnes-frontend, wiring egui’s per-frame pointer state into the existing EmuCore::set_mouse / EmuCore::set_superscope paths and updating docs/changelog to reflect the new behavior and a corrected gamepad-support claim.
Changes:
- Introduces
crate::peripheralsto sync Mouse (relative deltas + buttons) and Super Scope (absolute aim + buttons) once per frame. - Exposes
Gfx::letterbox_scaleaspub(crate)so input mapping can reuse the exact present-path letterbox transform. - Updates
docs/frontend.mdandCHANGELOG.mdto document the new live capture behavior and correct the prior “gilrs gamepad” claim.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/frontend.md | Updates frontend documentation to reflect live Mouse/Super Scope capture and corrects the prior port-1 “gilrs gamepad” statement. |
| crates/rustysnes-frontend/src/peripherals.rs | New module implementing per-frame host pointer → SNES peripheral input sync, with unit tests for mapping math. |
| crates/rustysnes-frontend/src/lib.rs | Exposes the new peripherals module from the frontend crate. |
| crates/rustysnes-frontend/src/gfx.rs | Makes letterbox_scale pub(crate) and documents why it’s reused by input mapping. |
| crates/rustysnes-frontend/src/app.rs | Calls crate::peripherals::sync in both synchronous and emu-thread present paths. |
| CHANGELOG.md | Adds an Unreleased entry describing the new live host-input capture and the doc correction. |
|
Closing and reopening to retrigger CI — the pull_request webhook event never fired for this PR (confirmed: only copilot-pull-request-reviewer's check-run exists on the head commit, no ci.yml run at all). |
New crate::peripherals module: egui::Context's pointer state now drives EmuCore::set_mouse (delta + left/right buttons) and set_superscope (absolute aim mapped through Gfx::letterbox_scale into SNES pixel space, trigger/cursor/turbo on left/right/middle mouse buttons) once per frame, on both the synchronous and emu-thread render paths. Mirrors rustysnes-libretro's own already-verified poll_port_input translation. Portable to wasm on purpose - no target_arch gate, since both the pointer API and the EmuCore calls are already platform-agnostic. 5 real unit tests cover the pure coordinate-mapping math directly. Also found and corrected a real, separate doc inaccuracy while scoping this: docs/frontend.md claimed controller port 1 has "keyboard + gilrs gamepad" input, but gilrs::Gilrs is never actually instantiated anywhere in this crate - port 1 is keyboard-only today. This is also why Super Multitap sub-pad host input stays unwired here: it needs that same, separate, larger prerequisite (a live Gilrs instance + event polling), not a small addition on top of this fix. Phase A.2 of the new UI/UX-parity ladder.
Two real bugs both bots independently flagged: - Mouse deltas were passed through raw (egui points, unscaled), so sensitivity varied with window scale and host display DPI (a 3x window scale felt 3x too fast). Convert to physical pixels via pixels_per_point, then rescale through the same letterbox transform Super Scope already used. - Super Scope mapped into the PPU's raw fb_dims, which can be pixel-doubled (512-wide hi-res, up to 448-tall interlaced) while SuperScopeState::set_input always clamps/offscreen-checks against the SNES's fixed base 256x224/239 screen space. Map into a new logical_snes_dims() instead, and apply the same pixels_per_point conversion the mouse path needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
65a118d to
1560e51
Compare
Summary
Phase A.2 of the new UI/UX-parity plan (RustySNES ↔ RustyNES).
crate::peripheralsmodule:egui::Context's pointer state now drivesEmuCore::set_mouse/set_superscopeonce per frame (both the synchronous andemu-threadrender paths) — the Settings → Input port-2 device selector already wired the emulated hardware
correctly since
v0.9.0, but nothing fed it real host input until now.letterbox transform (
Gfx::letterbox_scale, exposedpub(crate)for reuse rather thanre-derived) into SNES pixel space; trigger/cursor/turbo map to left/right/middle mouse buttons.
Mirrors
rustysnes-libretro's own already-verifiedpoll_port_inputtranslation.target_archgate) — both the pointer API and theEmuCorecalls are already platform-agnostic, so the hosted demo gets this too.
docs/frontend.mdclaimed controller port 1 has "keyboard + gilrs gamepad" input, but
gilrs::Gilrsis neveractually instantiated anywhere in this crate — port 1 is keyboard-only today. This is also why
Super Multitap sub-pad host input stays unwired: it needs that same, separate, larger
prerequisite (a live
Gilrsinstance + event polling), not a small addition on top of this fix.Tracked in the UI/UX-parity plan's Phase B/C backlog, not silently expanded into this PR.
Test plan
cargo test --workspace --exclude rustysnes-android— all greencargo clippy --workspace --exclude rustysnes-android --all-targets -- -D warnings— cleancargo clippy -p rustysnes-frontend --features full -- -D warnings— cleancargo clippy -p rustysnes-frontend --target wasm32-unknown-unknown --features wasm-winit -- -D warnings— cleancargo check/clippyforemu-thread— cleancargo fmt --check— cleanRUSTDOCFLAGS="-D warnings" cargo doc --workspace --exclude rustysnes-android --no-deps— cleancargo deny check— cleanperipherals.rs's own#[cfg(test)]module): centered/corner/pillarboxed-inside/pillarboxed-outside/off-window cases— not just "compiles."
🤖 Generated with Claude Code