Files
bevy/examples/window/monitor_info.rs
Sapein 947c8f2279 Implement a relationship between Windows and Monitors. (#23249)
# Objective
Currently there is no easy way to tell what Monitor a Window is on,
which is sometimes required for this like settings menus, without having
to tie yourself to `bevy_winit`.

This also fixes #19169

## Solution
This implements a Many-to-One relationship in `bevy_window`, which is
the `HasWindows` and `OnMonitor` relationship, which connects Monitors
and Windows together.

As a note there is currently one potential issue, in that the
relationship does not exist immediately when the Window is made, but
instead exists shortly after when the system to sync changes between
winit and the window component occurs. I'm not sure how to fix this as,
due to ordering, the Window technically exists prior to any Monitor
Entities being made at times.

## Testing
I've tested this on Void Linux X86-64, using the provided example. I
have not tested anything else.
2026-03-15 06:37:01 +00:00

96 lines
2.9 KiB
Rust

//! Displays information about available monitors (displays).
use bevy::{
camera::RenderTarget,
prelude::*,
window::{ExitCondition, Monitor, OnMonitor, WindowMode, WindowRef},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: None,
exit_condition: ExitCondition::DontExit,
..default()
}))
.add_systems(Update, (update, close_on_esc))
.run();
}
fn update(
mut commands: Commands,
monitors_added: Query<(Entity, &Monitor), Added<Monitor>>,
mut monitors_removed: RemovedComponents<Monitor>,
windows: Query<(Entity, &OnMonitor)>,
) {
for (entity, monitor) in monitors_added.iter() {
// Spawn a new window on each monitor
let name = monitor.name.clone().unwrap_or_else(|| "<no name>".into());
let size = format!("{}x{}px", monitor.physical_height, monitor.physical_width);
let hz = monitor
.refresh_rate_millihertz
.map(|x| format!("{}Hz", x as f32 / 1000.0))
.unwrap_or_else(|| "<unknown>".into());
let position = format!(
"x={} y={}",
monitor.physical_position.x, monitor.physical_position.y
);
let scale = format!("{:.2}", monitor.scale_factor);
let window = commands
.spawn((Window {
title: name.clone(),
mode: WindowMode::Fullscreen(
MonitorSelection::Entity(entity),
VideoModeSelection::Current,
),
position: WindowPosition::Centered(MonitorSelection::Entity(entity)),
..default()
},))
.id();
let camera = commands
.spawn((Camera2d, RenderTarget::Window(WindowRef::Entity(window))))
.id();
let info_text = format!(
"Monitor: {name}\nSize: {size}\nRefresh rate: {hz}\nPosition: {position}\nScale: {scale}\n\n",
);
commands.spawn((
Text(info_text),
Node {
position_type: PositionType::Relative,
height: percent(100),
width: percent(100),
..default()
},
UiTargetCamera(camera),
));
}
// Remove windows for removed monitors
for monitor_entity in monitors_removed.read() {
for (window_entity, on_monitor) in windows.iter() {
if on_monitor.0 == monitor_entity {
commands.entity(window_entity).despawn();
}
}
}
}
fn close_on_esc(
mut commands: Commands,
focused_windows: Query<(Entity, &Window)>,
input: Res<ButtonInput<KeyCode>>,
) {
for (window, focus) in focused_windows.iter() {
if !focus.focused {
continue;
}
if input.just_pressed(KeyCode::Escape) {
commands.entity(window).despawn();
}
}
}