bevy: Can't change title at runtime.

Bevy version

rev = “d158e0893ea88b6acf523a4264f076d6e0b540c6”

Operating system & version

Linux Mint 20.1 Ulyssa

What you did

I tried to change the window title:

fn update_title(score: Res<Score>, mut window: ResMut<WindowDescriptor>) {
    window.title = format!("Snake: {}", score.0);
}

What you expected to happen

I expected the title to change.

What actually happened

The title stayed the same.

Additional information

I am using a WindowDescriptor to set the title at startup. Window::set_title works, but is more verbose.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 22 (20 by maintainers)

Commits related to this issue

Most upvoted comments

Is there a reason we’re married to the idea that WindowDescriptor should only be used for initialisation?

There is one for me! It’s to avoid having two ways to do the same thing

There are a couple points to this.

The first is that it represents an unintuitive way of using resources*, but we have considered a special app.insert_setup_resource() method or better docs or renaming the type.

*unintuitive because:

  1. It fails silently if you insert it after DefaultPlugins
  2. It fails silently when you try to change it (as per OP)
  3. With #2886: a. if you had inserted it before DefaultPlugins, it crashes when you try to change it b. if you had inserted it after DefaultPlugins, it fails silently when you try to change it
  4. With or without #2886, it is a “dead” resource that can’t be interacted with

The second is that there are already several examples of having more than one way to do the same thing. In the best cases, there is usually an easy way in the general case, and a harder (more verbose) way that’s more flexible.

  • Text::with_section() vs Text { .. }
  • Res<Input<KeyCode>> vs EventReader<KeyboardInput>
  • input.any_pressed([a, b, c]) vs input.pressed(a) || input.pressed(b)) || input.pressed(c))
  • etc.

This:

fn update_title(score: Res<Score>, mut window: ResMut<WindowDescriptor>) {
    window.title = format!("Snake: {}", score.0);
}

represents an easy way in the general case (having one window). It’s easier because it’s intuitive: it matches the way that you initialise the window settings, and it matches the way you initialise and make changes to other resources (and components).

WindowDescriptor tracking would likely build on top of Windows sure, but I don’t see there being a problem of building one abstraction on top of another: look at Input<KeyCode>, which is a stateful abstraction over the KeyboardInput event.

Do you gain much by having that ^ over this:

fn update_title(score: Res<Score>, mut windows: ResMut<Windows>) {
    let window = windows.get_primary_mut().unwrap();
    window.set_title(format!("Snake: {}", score.0));
}

One less line perhaps, but other than that, nothing more than what’s already been discussed: I think the question is whether the clarity and usability win is enough.

I made comments on this topic earlier in the year https://github.com/bevyengine/bevy/issues/1255#issuecomment-771837798

My view for the specific issue at hand: WindowDescriptor (if that’s the resource used) ideally should be an “active” resource, in the same way as ClearColor. A user simply updates a resource, and things just work. This could be as simple as a system added to WindowPlugin that monitors changes to Res<WindowDescriptor>, and automatically applies the appropriate window.set_* methods. WindowDescriptor only applies to a single window, sure, but so does ClearColor.

I made a prototype of this a while ago (just updated to work with latest main)

mod window {
    use bevy::{prelude::*, window::WindowMode};

    #[derive(Default)]
    pub struct PrevWindow(WindowDescriptor);

    #[inline]
    fn compare_f32(a: f32, b: f32) -> bool {
        (a - b).abs() < std::f32::EPSILON
    }

    pub fn update_window(
        mut prev_window: ResMut<PrevWindow>,
        curr: Res<WindowDescriptor>,
        mut windows: ResMut<Windows>,
    ) {
        if curr.is_changed() {
            let window = windows.get_primary_mut().unwrap();
            let prev = &prev_window.0;

            if compare_f32(prev.width, curr.width) || compare_f32(prev.height, curr.height) {
                window.set_resolution(curr.width, curr.height);
            }
            if prev.scale_factor_override != curr.scale_factor_override {
                window.set_scale_factor_override(curr.scale_factor_override);
            }
            if prev.title != curr.title {
                window.set_title(curr.title.clone());
            }
            if prev.vsync != curr.vsync {
                window.set_vsync(curr.vsync);
            }
            if prev.resizable != curr.resizable {
                window.set_resizable(curr.resizable);
            }
            if prev.decorations != curr.decorations {
                window.set_decorations(curr.decorations);
            }
            if prev.cursor_visible != curr.cursor_visible {
                window.set_cursor_visibility(curr.cursor_visible);
            }
            if prev.cursor_locked != curr.cursor_locked {
                window.set_cursor_lock_mode(curr.cursor_locked);
            }
            match (prev.mode, curr.mode) {
                (WindowMode::Windowed, WindowMode::Windowed)
                | (WindowMode::BorderlessFullscreen, WindowMode::BorderlessFullscreen) => {}
                (
                    WindowMode::Fullscreen { use_size: p },
                    WindowMode::Fullscreen { use_size: c },
                ) => {
                    if p != c {
                        window.set_mode(curr.mode);
                    }
                }
                _ => {
                    window.set_mode(curr.mode);
                }
            }
            // there is no set_canvas method
            // #[cfg(target_arch = "wasm32")]
            // if prev.canvas != curr.canvas {
            //     window.set_canvas(curr.canvas);
            // }

            prev_window.0 = curr.clone();
        }
    }
}

use bevy::{prelude::*, window::WindowMode};

fn main() {
    App::new()
        .insert_resource(WindowDescriptor {
            title: "Press Escape to toggle full screen vs windowed. Hit a key to change the title."
                .into(),
            mode: WindowMode::Windowed,
            ..Default::default()
        })
        .add_plugins(DefaultPlugins)
        .init_resource::<window::PrevWindow>()
        .add_system(demo)
        .add_system(window::update_window)
        .run();
}

fn demo(input: Res<Input<KeyCode>>, mut window_descriptor: ResMut<WindowDescriptor>) {
    if input.just_pressed(KeyCode::Escape) {
        window_descriptor.mode = match window_descriptor.mode {
            WindowMode::Windowed => WindowMode::BorderlessFullscreen,
            WindowMode::BorderlessFullscreen => WindowMode::Windowed,
            _ => unreachable!(),
        }
    }
    for key in input.get_just_pressed() {
        window_descriptor.title = format!("Press Escape to toggle full screen vs windowed. Hit a key to change the title. You just pressed {:?}!", key);
    }
}

Hi guys, what about using a window as an entity with components like Descriptor and other?

I believe that is exactly what https://github.com/bevyengine/bevy/pull/5589 does.