egui-winit: Add ViewportBuilder::with_monitor and ViewportCommand::SetMonitor#8140
egui-winit: Add ViewportBuilder::with_monitor and ViewportCommand::SetMonitor#8140Le-Syl21 wants to merge 1 commit into
ViewportBuilder::with_monitor and ViewportCommand::SetMonitor#8140Conversation
|
Preview available at https://egui-pr-preview.github.io/pr/8140-viewport-with-monitor View snapshot changes at kitdiff |
… 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>
| let monitors: Vec<_> = event_loop.available_monitors().collect(); | ||
| if let Some(monitor) = monitors.get(idx) { |
There was a problem hiding this comment.
Why collect?
| let monitors: Vec<_> = event_loop.available_monitors().collect(); | |
| if let Some(monitor) = monitors.get(idx) { | |
| if let Some(monitor) = event_loop.available_monitors().nth(idx) { |
ViewportBuilder::with_monitor(index) and ViewportCommand::SetMonitor(index) route through winit's Fullscreen::Borderless(Some(MonitorHandle)), which is the only portable way to target a specific output on Wayland and avoids the Mutter race where OuterPosition is dropped before the window is mapped. Cross-platform: Windows, macOS, Linux X11, Linux Wayland.
708af0b to
14a4377
Compare
Le-Syl21
left a comment
There was a problem hiding this comment.
Good catch, applied. Force-pushed with the fix — nth(idx) in the success path, available_monitors().count() only re-evaluated when we actually need it for the diagnostic.
|
@Mingun good catch, applied — force-pushed. Uses |
| self | ||
| } | ||
|
|
||
| /// Place the window in borderless fullscreen on the monitor at `index`. |
There was a problem hiding this comment.
Hmm, it's weird that the window is always placed in borderless fullscreen if this is set, what if I want to place it on a specific window with a normal fullscreen or with a certain size?
Summary
Adds two paired API entry-points that let an integration target a specific monitor at viewport creation time, or move an existing viewport to a different monitor at runtime, in a way that works portably on Wayland.
Both route through winit's
Fullscreen::Borderless(Some(MonitorHandle)), which is the only portable mechanism that:OuterPosition)OuterPositionis dropped before the window is mapped (X11/Wayland-Mutter)with_positionandwith_outer_positioncontinue to work for cases where the integration does know the absolute pixel coordinates of each monitor and is on a platform where they are honored.with_monitoris the high-level alternative when you just want "show this window on output N, borderless fullscreen."Why this matters
Multi-monitor borderless setups (kiosks, pinball cabinets, museum installs, embedded panels) need each window to land on a specific physical display. Without
with_monitor:OuterPositionAPI.OuterPositionis silently ignored if applied before the window is mapped, and applied a few frames late if applied after — visible flicker as the window jumps.monitor.position()then sendingOuterPositionin a retry loop is the workaround pattern, but fragile and racy.Routing through
Fullscreen::Borderless(Some(MonitorHandle))is the same code path winit's own examples use for monitor-targeted fullscreen, just exposed at the egui ViewportBuilder level.Implementation
crates/egui/src/viewport.rs— addsmonitor: Option<usize>toViewportBuilder, thewith_monitor(usize)builder method, and theViewportCommand::SetMonitor(usize)variant.crates/egui-winit/src/lib.rs— both at viewport creation and onSetMonitor, look up the monitor by index inavailable_monitors()and applyFullscreen::Borderless(Some(handle)). Index out of range is a no-op (with alog::warn!), matching how unknown values are handled elsewhere in the file.73 lines added, 1 modified. No public API removed or changed.
Test plan
cargo build -p egui -p egui-winitcleancargo clippy -p egui -p egui-winit --all-features -- -D warningscleancargo fmt -p egui -p egui-winit --checkcleanFullscreen::Borderless(None)which is well-exercised on macOS, so I expect it works, but cabinet/multi-monitor on macOS is niche.Background
This is the third of three small upstream-able pieces extracted from the closed PR #8113 (viewport rotation, declined as too niche / too much surface). The rotation logic itself shipped as the standalone
egui-rotatecrate. The remaining two integration touch-points needed for kiosk/cabinet setups are:App::transform_primitives+App::post_platform_outputhooks (general-purpose post-tessellation / post-platform-output hooks)Key::ShiftLeft/Right+IntlBackslashphysical key variantswith_monitor/SetMonitorEach is independently useful. None depend on the others.
🤖 Drafted with Claude Code