-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Introduce WindowIcon component #21259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Welcome, new contributor! Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨ |
It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note. Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes. |
Hmm, usually you want to:
Doing it properly this way gives you better integration with things like app stores and app launchers. I'm not against providing this API, but we should note that it's not the best way to do things. |
…necessary. Add log_asset_messages to help understand the timing involved with window icon.
@mockersf has said effectively the same thing. At the least, I think we should clearly document how to do this effectively. Long-term, I would be interested in integrating this with tooling to create per-platform executables correctly, probably with |
I've identified this as a working command using Happy path:
2025-09-28T19:56:17.816273Z DEBUG bevy_asset::io::file: Asset Server using D:\Repos\Games\bevy\assets as its base path.
2025-09-28T19:56:18.543656Z INFO bevy_winit::system: Creating new window I am a window! (0v0)
2025-09-28T19:56:18.543979Z DEBUG bevy_winit::winit_windows: Display information: Window physical resolution: 500x300 Window logical resolution: 500x300 Monitor name: Scale factor: 0 Refresh rate (Hz): 0.000
2025-09-28T19:56:18.562441Z INFO window_settings: icon_handle: StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 0 }), path: Some(branding/icon.png) }
2025-09-28T19:56:18.567022Z INFO window_settings: msg=LoadedWithDependencies { id: AssetId<bevy_image::image::Image>{ index: 0, generation: 0} }
2025-09-28T19:56:18.572561Z DEBUG bevy_winit::system: Setting window icon window_entity=0v0 window.title="Seconds since startup: 0" window_icon.handle=StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 0 }), path: Some(branding/icon.png) } image_size=UVec2(256, 256)
2025-09-28T19:56:18.599191Z INFO window_settings: msg=Added { id: AssetId<bevy_image::image::Image>{uuid: 97128bb1-2588-480b-bdc6-87b4adbec477} }
2025-09-28T19:56:18.599302Z INFO window_settings: msg=Added { id: AssetId<bevy_image::image::Image>{uuid: d18ad97e-a322-4981-9505-44c59a4b5e46} }
2025-09-28T19:56:18.599396Z INFO window_settings: msg=Added { id: AssetId<bevy_image::image::Image>{ index: 0, generation: 0} } Problem path when not using all the necessary features; the asset never loads:
2025-09-28T20:06:16.122437Z DEBUG bevy_asset::io::file: Asset Server using D:\Repos\Games\bevy\assets as its base path.
2025-09-28T20:06:16.152055Z INFO bevy_winit::system: Creating new window I am a window! (0v0)
2025-09-28T20:06:16.152258Z DEBUG bevy_winit::winit_windows: Display information: Window physical resolution: 500x300 Window logical resolution: 500x300 Monitor name: Scale factor: 0 Refresh rate (Hz): 0.000
2025-09-28T20:06:16.164915Z INFO window_settings: icon_handle: StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 0 }), path: Some(branding/icon.png) }
2025-09-28T20:06:16.170763Z WARN bevy_winit::system: Could not set window icon for window: image asset not found window_entity=0v0 window=Window { present_mode: AutoVsync, mode: Windowed, position: Automatic, resolution: WindowResolution { physical_width: 500, physical_height: 300, scale_factor_override: None, scale_factor: 1.0 }, title: "Seconds since startup: 0", name: Some("bevy.app"), composite_alpha_mode: Auto, resize_constraints: WindowResizeConstraints { min_width: 180.0, min_height: 120.0, max_width: inf, max_height: inf }, resizable: true, enabled_buttons: EnabledButtons { minimize: true, maximize: false, close: true }, decorations: true, transparent: false, focused: true, window_level: Normal, canvas: None, fit_canvas_to_parent: true, prevent_default_event_handling: false, internal: InternalWindowState { minimize_request: None, maximize_request: None, drag_move_request: false, drag_resize_request: None, physical_cursor_position: None }, ime_enabled: false, ime_position: Vec2(0.0, 0.0), window_theme: Some(Dark), visible: false, skip_taskbar: false, clip_children: true, desired_maximum_frame_latency: None, recognize_pinch_gesture: false, recognize_rotation_gesture: false, recognize_doubletap_gesture: false, recognize_pan_gesture: None, movable_by_window_background: false, fullsize_content_view: false, has_shadow: true, titlebar_shown: true, titlebar_transparent: false, titlebar_show_title: true, titlebar_show_buttons: true, prefers_home_indicator_hidden: false, prefers_status_bar_hidden: false, preferred_screen_edges_deferring_system_gestures: None } window_icon=WindowIcon { handle: StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 0 }), path: Some(branding/icon.png) } }
2025-09-28T20:06:16.171429Z INFO window_settings: msg=Added { id: AssetId<bevy_image::image::Image>{uuid: 97128bb1-2588-480b-bdc6-87b4adbec477} }
2025-09-28T20:06:16.171538Z INFO window_settings: msg=Added { id: AssetId<bevy_image::image::Image>{uuid: d18ad97e-a322-4981-9505-44c59a4b5e46} } without |
You added a new feature but didn't update the readme. Please run |
Here's a summary of how I do exe icon setup for my Windows stuff usually Sample repo using Bevy in addition to some Windows-rs things to get a system tray icon: https://github.com/teamdman/youre-muted-btw
# create .ico
magick convert -background transparent "icon.png" -define icon:auto-resize=16,24,32,48,64,72,96,128,256 "favicon.ico"
# create resized image
magick convert -background transparent "icon.png" -resize 64x64 ".\assets\textures\icon.png"
[build-dependencies]
embed-resource = "3.0.3"
I use
extern crate embed_resource;
fn main() {
println!("cargo:rerun-if-changed=icons.rc");
println!("cargo:rerun-if-changed=favicon.ico");
embed_resource::compile("icons.rc", embed_resource::NONE)
.manifest_required()
.unwrap();
} This should result in the exe having the correct icon ![]() Additionally, we can retrieve the icon from the exe as an HICON // Load the icon from embedded resources using LoadIconW
let icon = {
let instance = GetModuleHandleW(None)?;
let resource_name = w!("aaa_my_icon");
match LoadIconW(Some(HINSTANCE(instance.0)), resource_name) {
Ok(hicon) => hicon,
Err(e) => {
error!("Failed to load icon resource 'aaa_my_icon': {e}");
// Fallback to default application icon
match LoadIconW(None, IDI_APPLICATION) {
Ok(fallback_icon) => fallback_icon,
Err(fallback_error) => {
error!(
"Failed to load fallback IDI_APPLICATION icon: {fallback_error}"
);
return Err(fallback_error.into());
}
}
}
}
}; HICON can be converted to more friendly image types; see https://stackoverflow.com/a/78190249/11141271 I have in the past successfully converted HICONs into images and added them as textures for the asset system fn receive(
mut commands: Commands,
mut bridge: EventReader<GameboundMessage>,
mut icons_so_far: Local<usize>,
mut textures: ResMut<Assets<Image>>,
) {
for msg in bridge.read() {
match msg {
GameboundMessage::RunningProcessIcons(icons) => {
info!("Received icons: {:?}", icons.len());
for (exe_path, images) in icons {
for image in images {
debug!("{}x{}", image.width(), image.height());
let dynamic = DynamicImage::ImageRgba8(image.clone());
let handle = textures.add(Image::from_dynamic(dynamic, true));
... However I haven't gone as far as generalizing the At the time it was easier to just have my |
This PR is for the window icon, not the application icon. it's something that exists only on windows and on x11, see https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_window_icon |
Correct, just wanted to summarize my own experience regarding exe icons as it was mentioned |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a WindowIcon
component to enable setting window icons through Bevy's existing asset pipeline. The implementation provides a component-based approach where users can attach an image asset handle to a window entity to customize its icon.
- Adds a new
WindowIcon
component that holds aHandle<Image>
for the window icon - Implements a system to monitor changes to the
WindowIcon
component and update the window icon accordingly - Adds a new
custom_window_icon
feature flag to control the availability of this functionality
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
examples/window/window_settings.rs | Adds example usage of the new WindowIcon component with feature-gated code |
docs/cargo_features.md | Documents the new custom_window_icon feature |
crates/bevy_winit/src/system.rs | Implements the core system that handles WindowIcon changes and sets the winit window icon |
crates/bevy_winit/src/lib.rs | Registers the new window icon system with the plugin |
crates/bevy_winit/Cargo.toml | Adds dependencies and feature configuration for custom_window_icon |
crates/bevy_window/src/window.rs | Defines the WindowIcon component struct |
crates/bevy_window/Cargo.toml | Adds feature configuration for custom_window_icon |
crates/bevy_internal/Cargo.toml | Propagates the custom_window_icon feature flag |
Cargo.toml | Adds custom_window_icon to default features and example requirements |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Problem identified: I had relied on 2025-09-30T01:54:58.703034Z INFO bevy_winit::system: Creating new window I am a window! (0v0)
2025-09-30T01:54:58.713488Z INFO window_settings: icon_handle: StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 7 }), path: Some(branding/icon.png) }
2025-09-30T01:54:58.729812Z INFO window_settings: msg=LoadedWithDependencies { id: AssetId<bevy_image::image::Image>{ index: 5, generation: 0} }
2025-09-30T01:54:58.730128Z INFO window_settings: msg=LoadedWithDependencies { id: AssetId<bevy_image::image::Image>{ index: 4, generation: 0} }
2025-09-30T01:54:58.730380Z INFO window_settings: msg=LoadedWithDependencies { id: AssetId<bevy_image::image::Image>{ index: 8, generation: 0} }
2025-09-30T01:54:58.730602Z INFO window_settings: msg=LoadedWithDependencies { id: AssetId<bevy_image::image::Image>{ index: 7, generation: 0} }
2025-09-30T01:54:58.771542Z WARN bevy_winit::system: Could not set window icon for window: winit window not found window_entity=14v0 window=Window { present_mode: Fifo, mode: Windowed, position: Automatic, resolution: WindowResolution { physical_width: 300, physical_height: 200, scale_factor_override: None, scale_factor: 1.0 }, title: "I am another window!", name: None, composite_alpha_mode: Auto, resize_constraints: WindowResizeConstraints { min_width: 180.0, min_height: 120.0, max_width: inf, max_height: inf }, resizable: true, enabled_buttons: EnabledButtons { minimize: true, maximize: true, close: true }, decorations: true, transparent: false, focused: true, window_level: Normal, canvas: None, fit_canvas_to_parent: false, prevent_default_event_handling: true, internal: InternalWindowState { minimize_request: None, maximize_request: None, drag_move_request: false, drag_resize_request: None, physical_cursor_position: None }, ime_enabled: false, ime_position: Vec2(0.0, 0.0), window_theme: None, visible: true, skip_taskbar: false, clip_children: true, desired_maximum_frame_latency: None, recognize_pinch_gesture: false, recognize_rotation_gesture: false, recognize_doubletap_gesture: false, recognize_pan_gesture: None, movable_by_window_background: false, fullsize_content_view: false, has_shadow: true, titlebar_shown: true, titlebar_transparent: false, titlebar_show_title: true, titlebar_show_buttons: true, prefers_home_indicator_hidden: false, prefers_status_bar_hidden: false, preferred_screen_edges_deferring_system_gestures: None } window_icon=WindowIcon { handle: StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 8 }), path: Some(textures/rpg/props/generic-rpg-tree02.png) } }
2025-09-30T01:54:58.819031Z WARN bevy_winit::system: Disabling window-icon-on-init for testing.
2025-09-30T01:54:58.819302Z INFO bevy_winit::system: Creating new window I am another window! (14v0) Note that the ![]() sequenceDiagram
participant World
participant System
participant Winit
World->>World: Spawn Entity with Window and WindowIcon
System->>World: Query Changed<WindowIcon>
System->>Winit: Look for winit window
Winit-->>System: ❌ Not found
Note over System: Failure: winit window<br/>doesn't exist yet
Winit->>Winit: Create winit window
I previously solved this by having the system using This approach worked in the other project, but for this PR it sounds more reasonable to simply set the icon during window construction and have an internal This is working, supporting windows beyond just the first PrimaryWindow sequenceDiagram
participant World
participant System
participant Winit
World->>World: Spawn Entity with Window and WindowIcon
System->>World: Query Changed<WindowIcon>
System->>Winit: Look for winit window
Winit-->>System: ❌ Not found
Note over System: Debug log: assume<br/>not ready yet
Winit->>Winit: Create winit window
rect rgb(50, 155, 100)
Winit->>Winit: Set icon ✓
end
![]() |
…d system timing not working for late created windows
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
In my real application I'm now encountering the race condition where the window is created before the asset is ready. My logsteamy-mft on HEAD (230b685) [!?] is 📦 v0.3.0 via 🦀 v1.91.0-nightly took 4m13s
❯ cargo run -- engine run --debug
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.90s
Running `target\debug\teamy-mft.exe engine run --debug`
DEBUG teamy_mft: Tracing initialized with level: Level(Debug)
DEBUG teamy_mft::engine::run: Building Bevy engine
INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Windows 10 Pro", kernel: "19045", cpu: "AMD Ryzen 9 5950X 16-Core Processor", core_count: "16", memory: "63.9 GiB" }
DEBUG bevy_asset::io::file: Asset Server using G:\Programming\Repos\teamy-mft\assets as its base path.
INFO bevy_render::renderer: AdapterInfo { name: "NVIDIA GeForce RTX 4090", vendor: 4318, device: 9860, device_type: DiscreteGpu, driver: "NVIDIA", driver_info: "580.97", backend: Vulkan }
DEBUG teamy_mft::engine::run: Bevy engine built
INFO teamy_mft::engine::run: Running Bevy engine
INFO bevy_render::batching::gpu_preprocessing: GPU preprocessing is fully supported on this device.
DEBUG teamy_mft::engine::sync_dir_plugin: [DISABLED] Emitted ReadSyncDirectory event on startup
INFO teamy_mft::engine::mft_file_overview_window_plugin: Spawned MFT overview window, title: "MFT Files - Overview"
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: LoadedWithDependencies { id: AssetId<bevy_image::image::Image>{ index: 5, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: LoadedWithDependencies { id: AssetId<bevy_image::image::Image>{ index: 4, generation: 0} }
DEBUG bevy_winit::system: Could not set window icon for window: winit window not found. Assuming that the winit window is not yet created, so the icon will instead be applied during window creation., window_entity: 13v0, window: Window { present_mode: Fifo, mode: Windowed, position: Automatic, resolution: WindowResolution { physical_width: 1280, physical_height: 720, scale_factor_override: None, scale_factor: 1.0 }, title: "MFT Files - Overview", name: None, composite_alpha_mode: Auto, resize_constraints: WindowResizeConstraints { min_width: 180.0, min_height: 120.0, max_width: inf, max_height: inf }, resizable: true, enabled_buttons: EnabledButtons { minimize: true, maximize: true, close: true }, decorations: true, transparent: false, focused: true, window_level: Normal, canvas: None, fit_canvas_to_parent: false, prevent_default_event_handling: true, internal: InternalWindowState { minimize_request: None, maximize_request: None, drag_move_request: false, drag_resize_request: None, physical_cursor_position: None }, ime_enabled: false, ime_position: Vec2(0.0, 0.0), window_theme: None, visible: true, skip_taskbar: false, clip_children: true, desired_maximum_frame_latency: None, recognize_pinch_gesture: false, recognize_rotation_gesture: false, recognize_doubletap_gesture: false, recognize_pan_gesture: None, movable_by_window_background: false, fullsize_content_view: false, has_shadow: true, titlebar_shown: true, titlebar_transparent: false, titlebar_show_title: true, titlebar_show_buttons: true, prefers_home_indicator_hidden: false, prefers_status_bar_hidden: false, preferred_screen_edges_deferring_system_gestures: None }, window_icon: WindowIcon { handle: StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 7 }), path: Some(textures/icon.png) } }
INFO bevy_winit::system: Creating new window MFT Files - Overview (13v0)
WARN bevy_winit::winit_window_icon: Could not create winit window icon from Bevy assets: image asset not found, image_handle: StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 7 }), path: Some(textures/icon.png) }
WARN bevy_winit::system: Could not set window icon for window: failed to acquire winit window icon, entity: 13v0, window: Mut(Window { present_mode: Fifo, mode: Windowed, position: Automatic, resolution: WindowResolution { physical_width: 1280, physical_height: 720, scale_factor_override: None, scale_factor: 1.0 }, title: "MFT Files - Overview", name: None, composite_alpha_mode: Auto, resize_constraints: WindowResizeConstraints { min_width: 180.0, min_height: 120.0, max_width: inf, max_height: inf }, resizable: true, enabled_buttons: EnabledButtons { minimize: true, maximize: true, close: true }, decorations: true, transparent: false, focused: true, window_level: Normal, canvas: None, fit_canvas_to_parent: false, prevent_default_event_handling: true, internal: InternalWindowState { minimize_request: None, maximize_request: None, drag_move_request: false, drag_resize_request: None, physical_cursor_position: None }, ime_enabled: false, ime_position: Vec2(0.0, 0.0), window_theme: None, visible: true, skip_taskbar: false, clip_children: true, desired_maximum_frame_latency: None, recognize_pinch_gesture: false, recognize_rotation_gesture: false, recognize_doubletap_gesture: false, recognize_pan_gesture: None, movable_by_window_background: false, fullsize_content_view: false, has_shadow: true, titlebar_shown: true, titlebar_transparent: false, titlebar_show_title: true, titlebar_show_buttons: true, prefers_home_indicator_hidden: false, prefers_status_bar_hidden: false, preferred_screen_edges_deferring_system_gestures: None }), window_icon: WindowIcon { handle: StrongHandle<Image>{ id: Index(AssetIndex { generation: 0, index: 7 }), path: Some(textures/icon.png) } }
DEBUG bevy_winit::winit_windows: Display information: Window physical resolution: 1280x720 Window logical resolution: 1280x720 Monitor name: Scale factor: 0 Refresh rate (Hz): 0.000
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{uuid: 97128bb1-2588-480b-bdc6-87b4adbec477} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{uuid: d18ad97e-a322-4981-9505-44c59a4b5e46} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{ index: 0, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{ index: 1, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{ index: 2, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{ index: 3, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{ index: 6, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{ index: 5, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{ index: 4, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: LoadedWithDependencies { id: AssetId<bevy_image::image::Image>{ index: 7, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Removed { id: AssetId<bevy_image::image::Image>{ index: 6, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Removed { id: AssetId<bevy_image::image::Image>{ index: 3, generation: 0} }
DEBUG teamy_mft::engine::assets::asset_message_log_plugin: Image asset event, msg: Added { id: AssetId<bevy_image::image::Image>{ index: 7, generation: 0} }
DEBUG bevy_time::virt: delta time larger than maximum delta, clamping delta to 250ms and skipping 402.8105ms
INFO bevy_winit::system: Closing window 13v0 |
Defined marker component for when winit icon update should be attemped. Insert marker component when assets are changed or made ready.
/// Identify all windows that had their [`WindowIcon`] underlying asset change and mark them for updating the winit icon | ||
#[cfg(feature = "custom_window_icon")] | ||
pub(crate) fn changed_window_icon_asset( | ||
mut commands: bevy_ecs::system::Commands, | ||
windows: Query<(Entity, &WindowIcon)>, | ||
mut asset_messages: MessageReader<AssetEvent<Image>>, | ||
) { | ||
for event in asset_messages.read() { | ||
if let AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } = event { | ||
for (entity, window_icon) in &windows { | ||
if window_icon.handle.id() == *id { | ||
commands.entity(entity).insert(WindowIconRefreshNeeded); | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently using naïve loop to identify the windows using the handle corresponding to the changed asset.
Could get fancy with observers to maintain a lookup table or something, but for now I went with the low complexity solution.
Here's an attempt at a diagram lol flowchart TD
Start([Window with WindowIcon spawned]) --> CheckWindow{Window created<br/>in winit?}
CheckWindow -->|No| CreateWindow[create_windows system<br/>creates winit window]
CheckWindow -->|Yes| MarkRefresh[Insert WindowIconRefreshNeeded]
CreateWindow --> CheckAssetReady{Image asset<br/>ready?}
CheckAssetReady -->|Yes| SetIconDuringCreation[Set icon during<br/>window creation]
CheckAssetReady -->|No| LogWillRetry[Log: will try when<br/>asset is ready]
SetIconDuringCreation --> Success1([Icon set ✓])
LogWillRetry --> WaitForAsset[Wait for asset]
WaitForAsset --> AssetEvent{Asset LoadedWithDependencies<br/>or Modified event}
AssetEvent --> MarkRefreshAsset[changed_window_icon_asset<br/>inserts WindowIconRefreshNeeded]
MarkRefreshAsset --> SetWinitIcon[set_winit_window_icon system]
MarkRefresh --> SetWinitIcon
SetWinitIcon --> CheckWindowExists{Winit window<br/>exists?}
CheckWindowExists -->|No| LogWindowNotFound[Log: winit window not found<br/>will try during creation]
CheckWindowExists -->|Yes| CheckAssetExists{Image asset<br/>exists?}
LogWindowNotFound --> RemoveMarker1[Remove WindowIconRefreshNeeded]
RemoveMarker1 --> WaitForWindowCreation[Wait for window creation]
WaitForWindowCreation --> CreateWindow
CheckAssetExists -->|No| LogAssetNotFound[Log: asset not found<br/>will try when ready]
CheckAssetExists -->|Yes| ConvertIcon{Convert to<br/>winit Icon}
LogAssetNotFound --> RemoveMarker2[Remove WindowIconRefreshNeeded]
RemoveMarker2 --> WaitForAsset
ConvertIcon -->|Success| ApplyIcon[winit_window.set_window_icon]
ConvertIcon -->|Failure| LogConversionFailed[Warn: failed to create<br/>winit window icon]
ApplyIcon --> RemoveMarker3[Remove WindowIconRefreshNeeded]
LogConversionFailed --> RemoveMarker3
RemoveMarker3 --> Success2([Icon set ✓])
%% Handle changes
WindowIconChanged[WindowIcon component changed] --> ChangedSystem[changed_window_icon system]
ChangedSystem --> MarkRefresh
AssetChanged[Image asset Modified event] --> AssetChangedSystem[changed_window_icon_asset system]
AssetChangedSystem --> MarkRefreshAsset
style Success1 fill:#2D5016
style Success2 fill:#2D5016
style LogWillRetry fill:#8B6914
style LogWindowNotFound fill:#8B6914
style LogAssetNotFound fill:#8B6914
style LogConversionFailed fill:#8B1538
|
On Windows, winit provides an API to set window icon to the app icon like so: use bevy_app::{App, Plugin, Startup};
use bevy_ecs::system::NonSend;
use bevy_winit::WinitWindows;
use winit::platform::windows::IconExtWindows;
/// Only works on Windows
pub struct WindowsIconPlugin;
impl Plugin for WindowsIconPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, set_window_icon);
}
}
fn set_window_icon(windows: NonSend<WinitWindows>) {
// 32512 is a magic number representing the default application icon
let icon = IconExtWindows::from_resource(32512, None).ok();
for window in windows.windows.values() {
window.set_window_icon(icon.clone());
}
} I think this should be considered the default way to set the icon because it avoids relying on the asset system. |
As the user I prefer to use Bevy principals like including a component in the bundle when creating a window rather than setting up a separate system for dealing with the timing of winit window availability to set the icon in addition to reimplementing asset loading for when I want to use non-builtin icons in addition to converting formats of the image. |
Sorry I wasn't that clear but I think that bevy should by default set the window icon to the app icon in addition to providing the functionality you've provided in this PR. They're separate features and should probably be implemented in separate PRs. I wanted to bring it up because most apps want the window icon to be the same as the app icon and this PR doesn't provide a way to do that easily. |
Objective
Related:
Solution
WindowIcon { handle: Handle<Image> }
component that can be added to entities with theWindow
component.Testing
It working:
cargo run --example window_settings
It disabled:
cargo run --example window_settings --no-default-features --features bevy_window,bevy_winit,bevy_log
Tested on Windows 10.
Showcase
Caveats
It works with the default features, but trying to manually enable them it is not working lol
I suspect this is either because:
Both scenarios should be an easy fix. I will proceed with investigating by adding a system to print AssetEvent messages to see what's going on
Additionally, this is currently using the Changed event to detect added/modified component, and is not setting the icon when the window is created if the component is present. This means that there is a small delay between the window's creation and the first setting of the icon, but in practice it should be unnoticeable :P