window_settings/
window_settings.rs

1//! Illustrates how to change window settings and shows how to affect
2//! the mouse pointer in various ways.
3
4#[cfg(feature = "custom_cursor")]
5use bevy::window::{CustomCursor, CustomCursorImage};
6use bevy::{
7    diagnostic::{FrameCount, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
8    prelude::*,
9    window::{
10        CursorGrabMode, CursorIcon, CursorOptions, PresentMode, SystemCursorIcon, WindowLevel,
11        WindowTheme,
12    },
13};
14
15fn main() {
16    App::new()
17        .add_plugins((
18            DefaultPlugins.set(WindowPlugin {
19                primary_window: Some(Window {
20                    title: "I am a window!".into(),
21                    name: Some("bevy.app".into()),
22                    resolution: (500, 300).into(),
23                    present_mode: PresentMode::AutoVsync,
24                    // Tells Wasm to resize the window according to the available canvas
25                    fit_canvas_to_parent: true,
26                    // Tells Wasm not to override default event handling, like F5, Ctrl+R etc.
27                    prevent_default_event_handling: false,
28                    window_theme: Some(WindowTheme::Dark),
29                    enabled_buttons: bevy::window::EnabledButtons {
30                        maximize: false,
31                        ..Default::default()
32                    },
33                    // This will spawn an invisible window
34                    // The window will be made visible in the make_visible() system after 3 frames.
35                    // This is useful when you want to avoid the white window that shows up before the GPU is ready to render the app.
36                    visible: false,
37                    ..default()
38                }),
39                ..default()
40            }),
41            LogDiagnosticsPlugin::default(),
42            FrameTimeDiagnosticsPlugin::default(),
43        ))
44        .add_systems(Startup, init_cursor_icons)
45        .add_systems(
46            Update,
47            (
48                change_title,
49                toggle_theme,
50                toggle_cursor,
51                toggle_vsync,
52                toggle_window_controls,
53                cycle_cursor_icon,
54                switch_level,
55                make_visible,
56            ),
57        )
58        .run();
59}
60
61fn make_visible(mut window: Single<&mut Window>, frames: Res<FrameCount>) {
62    // The delay may be different for your app or system.
63    if frames.0 == 3 {
64        // At this point the gpu is ready to show the app so we can make the window visible.
65        // Alternatively, you could toggle the visibility in Startup.
66        // It will work, but it will have one white frame before it starts rendering
67        window.visible = true;
68    }
69}
70
71/// This system toggles the vsync mode when pressing the button V.
72/// You'll see fps increase displayed in the console.
73fn toggle_vsync(input: Res<ButtonInput<KeyCode>>, mut window: Single<&mut Window>) {
74    if input.just_pressed(KeyCode::KeyV) {
75        window.present_mode = if matches!(window.present_mode, PresentMode::AutoVsync) {
76            PresentMode::AutoNoVsync
77        } else {
78            PresentMode::AutoVsync
79        };
80        info!("PRESENT_MODE: {:?}", window.present_mode);
81    }
82}
83
84/// This system switches the window level when pressing the T button
85/// You'll notice it won't be covered by other windows, or will be covered by all the other
86/// windows depending on the level.
87///
88/// This feature only works on some platforms. Please check the
89/// [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.Window.html#structfield.window_level)
90/// for more details.
91fn switch_level(input: Res<ButtonInput<KeyCode>>, mut window: Single<&mut Window>) {
92    if input.just_pressed(KeyCode::KeyT) {
93        window.window_level = match window.window_level {
94            WindowLevel::AlwaysOnBottom => WindowLevel::Normal,
95            WindowLevel::Normal => WindowLevel::AlwaysOnTop,
96            WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnBottom,
97        };
98        info!("WINDOW_LEVEL: {:?}", window.window_level);
99    }
100}
101
102/// This system toggles the window controls when pressing buttons 1, 2 and 3
103///
104/// This feature only works on some platforms. Please check the
105/// [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.Window.html#structfield.enabled_buttons)
106/// for more details.
107fn toggle_window_controls(input: Res<ButtonInput<KeyCode>>, mut window: Single<&mut Window>) {
108    let toggle_minimize = input.just_pressed(KeyCode::Digit1);
109    let toggle_maximize = input.just_pressed(KeyCode::Digit2);
110    let toggle_close = input.just_pressed(KeyCode::Digit3);
111
112    if toggle_minimize || toggle_maximize || toggle_close {
113        if toggle_minimize {
114            window.enabled_buttons.minimize = !window.enabled_buttons.minimize;
115        }
116        if toggle_maximize {
117            window.enabled_buttons.maximize = !window.enabled_buttons.maximize;
118        }
119        if toggle_close {
120            window.enabled_buttons.close = !window.enabled_buttons.close;
121        }
122    }
123}
124
125/// This system will then change the title during execution
126fn change_title(mut window: Single<&mut Window>, time: Res<Time>) {
127    window.title = format!(
128        "Seconds since startup: {}",
129        time.elapsed().as_secs_f32().round()
130    );
131}
132
133fn toggle_cursor(mut cursor_options: Single<&mut CursorOptions>, input: Res<ButtonInput<KeyCode>>) {
134    if input.just_pressed(KeyCode::Space) {
135        cursor_options.visible = !cursor_options.visible;
136        cursor_options.grab_mode = match cursor_options.grab_mode {
137            CursorGrabMode::None => CursorGrabMode::Locked,
138            CursorGrabMode::Locked | CursorGrabMode::Confined => CursorGrabMode::None,
139        };
140    }
141}
142
143// This system will toggle the color theme used by the window
144fn toggle_theme(mut window: Single<&mut Window>, input: Res<ButtonInput<KeyCode>>) {
145    if input.just_pressed(KeyCode::KeyF)
146        && let Some(current_theme) = window.window_theme
147    {
148        window.window_theme = match current_theme {
149            WindowTheme::Light => Some(WindowTheme::Dark),
150            WindowTheme::Dark => Some(WindowTheme::Light),
151        };
152    }
153}
154
155#[derive(Resource)]
156struct CursorIcons(Vec<CursorIcon>);
157
158fn init_cursor_icons(
159    mut commands: Commands,
160    #[cfg(feature = "custom_cursor")] asset_server: Res<AssetServer>,
161) {
162    commands.insert_resource(CursorIcons(vec![
163        SystemCursorIcon::Default.into(),
164        SystemCursorIcon::Pointer.into(),
165        SystemCursorIcon::Wait.into(),
166        SystemCursorIcon::Text.into(),
167        #[cfg(feature = "custom_cursor")]
168        CustomCursor::Image(CustomCursorImage {
169            handle: asset_server.load("branding/icon.png"),
170            hotspot: (128, 128),
171            ..Default::default()
172        })
173        .into(),
174    ]));
175}
176
177/// This system cycles the cursor's icon through a small set of icons when clicking
178fn cycle_cursor_icon(
179    mut commands: Commands,
180    window: Single<Entity, With<Window>>,
181    input: Res<ButtonInput<MouseButton>>,
182    mut index: Local<usize>,
183    cursor_icons: Res<CursorIcons>,
184) {
185    if input.just_pressed(MouseButton::Left) {
186        *index = (*index + 1) % cursor_icons.0.len();
187        commands
188            .entity(*window)
189            .insert(cursor_icons.0[*index].clone());
190    } else if input.just_pressed(MouseButton::Right) {
191        *index = if *index == 0 {
192            cursor_icons.0.len() - 1
193        } else {
194            *index - 1
195        };
196        commands
197            .entity(*window)
198            .insert(cursor_icons.0[*index].clone());
199    }
200}