From b72b15465d1dae56381116f383f5a781f98aa011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=9E=97=E4=BC=9F?= Date: Tue, 6 Jun 2023 05:04:22 +0800 Subject: [PATCH] Support to set window theme and expose system window theme changed event (#8593) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Fixes https://github.com/bevyengine/bevy/issues/8586. ## Solution - Add `preferred_theme` field to `Window` and set it when window creation - Add `window_theme` field to `InternalWindowState` to store current window theme - Expose winit `WindowThemeChanged` event --------- Co-authored-by: hate <15314665+hate@users.noreply.github.com> Co-authored-by: Nicola Papale Co-authored-by: Alice Cecile Co-authored-by: François --- crates/bevy_window/src/event.rs | 18 ++++++++++++++++++ crates/bevy_window/src/lib.rs | 9 ++++++--- crates/bevy_window/src/window.rs | 25 +++++++++++++++++++++++++ crates/bevy_winit/src/converters.rs | 16 +++++++++++++++- crates/bevy_winit/src/lib.rs | 10 +++++++++- crates/bevy_winit/src/system.rs | 11 ++++++++++- crates/bevy_winit/src/winit_windows.rs | 3 ++- examples/window/window_settings.rs | 18 +++++++++++++++++- 8 files changed, 102 insertions(+), 8 deletions(-) diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index 59b30bf396..a1e4a4bda5 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -7,6 +7,8 @@ use bevy_reflect::{FromReflect, Reflect}; #[cfg(feature = "serialize")] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; +use crate::WindowTheme; + /// A window event that is sent whenever a window's logical size has changed. #[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] #[reflect(Debug, PartialEq)] @@ -289,3 +291,19 @@ pub struct WindowMoved { /// Where the window moved to in physical pixels. pub position: IVec2, } + +/// An event sent when system changed window theme. +/// +/// This event is only sent when the window is relying on the system theme to control its appearance. +/// i.e. It is only sent when [`Window::window_theme`](crate::window::Window::window_theme) is `None` and the system theme changes. +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct WindowThemeChanged { + pub window: Entity, + pub theme: WindowTheme, +} diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index e8c42bd340..99872db081 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -84,7 +84,8 @@ impl Plugin for WindowPlugin { .add_event::() .add_event::() .add_event::() - .add_event::(); + .add_event::() + .add_event::(); if let Some(primary_window) = &self.primary_window { app.world @@ -121,7 +122,8 @@ impl Plugin for WindowPlugin { .register_type::() .register_type::() .register_type::() - .register_type::(); + .register_type::() + .register_type::(); // Register window descriptor and related types app.register_type::() @@ -136,7 +138,8 @@ impl Plugin for WindowPlugin { .register_type::() .register_type::() .register_type::() - .register_type::(); + .register_type::() + .register_type::(); // Register `PathBuf` as it's used by `FileDragAndDrop` app.register_type::(); diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 96e43a7c59..b027a4f096 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -184,6 +184,14 @@ pub struct Window { /// /// - iOS / Android / Web: Unsupported. pub ime_position: Vec2, + /// Sets a specific theme for the window. + /// + /// If `None` is provided, the window will use the system theme. + /// + /// ## Platform-specific + /// + /// - iOS / Android / Web: Unsupported. + pub window_theme: Option, } impl Default for Window { @@ -208,6 +216,7 @@ impl Default for Window { fit_canvas_to_parent: false, prevent_default_event_handling: true, canvas: None, + window_theme: None, } } } @@ -832,3 +841,19 @@ pub enum WindowLevel { /// The window will always be on top of normal windows. AlwaysOnTop, } + +/// The window theme variant to use. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq)] +pub enum WindowTheme { + /// Use the light variant. + Light, + + /// Use the dark variant. + Dark, +} diff --git a/crates/bevy_winit/src/converters.rs b/crates/bevy_winit/src/converters.rs index 6da825eb03..65591f6b99 100644 --- a/crates/bevy_winit/src/converters.rs +++ b/crates/bevy_winit/src/converters.rs @@ -5,7 +5,7 @@ use bevy_input::{ ButtonState, }; use bevy_math::Vec2; -use bevy_window::{CursorIcon, WindowLevel}; +use bevy_window::{CursorIcon, WindowLevel, WindowTheme}; pub fn convert_keyboard_input(keyboard_input: &winit::event::KeyboardInput) -> KeyboardInput { KeyboardInput { @@ -274,3 +274,17 @@ pub fn convert_window_level(window_level: WindowLevel) -> winit::window::WindowL WindowLevel::AlwaysOnTop => winit::window::WindowLevel::AlwaysOnTop, } } + +pub fn convert_winit_theme(theme: winit::window::Theme) -> WindowTheme { + match theme { + winit::window::Theme::Light => WindowTheme::Light, + winit::window::Theme::Dark => WindowTheme::Dark, + } +} + +pub fn convert_window_theme(theme: WindowTheme) -> winit::window::Theme { + match theme { + WindowTheme::Light => winit::window::Theme::Light, + WindowTheme::Dark => winit::window::Theme::Dark, + } +} diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index c56ef06cd6..8957feac65 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -41,7 +41,7 @@ use bevy_window::{ exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowFocused, WindowMoved, WindowResized, - WindowScaleFactorChanged, + WindowScaleFactorChanged, WindowThemeChanged, }; #[cfg(target_os = "android")] @@ -54,6 +54,7 @@ use winit::{ use crate::accessibility::{AccessKitAdapters, AccessibilityPlugin, WinitActionHandlers}; +use crate::converters::convert_winit_theme; #[cfg(target_arch = "wasm32")] use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin}; @@ -227,6 +228,7 @@ struct WindowEvents<'w> { window_backend_scale_factor_changed: EventWriter<'w, WindowBackendScaleFactorChanged>, window_focused: EventWriter<'w, WindowFocused>, window_moved: EventWriter<'w, WindowMoved>, + window_theme_changed: EventWriter<'w, WindowThemeChanged>, } #[derive(SystemParam)] @@ -613,6 +615,12 @@ pub fn winit_runner(mut app: App) { window: window_entity, }), }, + WindowEvent::ThemeChanged(theme) => { + window_events.window_theme_changed.send(WindowThemeChanged { + window: window_entity, + theme: convert_winit_theme(theme), + }); + } _ => {} } diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 996ddf1b67..92a3ec2ca9 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -23,7 +23,7 @@ use winit::{ use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR}; use crate::{ accessibility::{AccessKitAdapters, WinitActionHandlers}, - converters::{self, convert_window_level}, + converters::{self, convert_window_level, convert_window_theme, convert_winit_theme}, get_best_videomode, get_fitting_videomode, WinitWindows, }; @@ -62,6 +62,11 @@ pub(crate) fn create_window<'a>( &mut handlers, &mut accessibility_requested, ); + + if let Some(theme) = winit_window.theme() { + window.window_theme = Some(convert_winit_theme(theme)); + } + window .resolution .set_scale_factor(winit_window.scale_factor()); @@ -296,6 +301,10 @@ pub(crate) fn changed_window( )); } + if window.window_theme != cache.window.window_theme { + winit_window.set_theme(window.window_theme.map(convert_window_theme)); + } + cache.window = window.clone(); } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 6bc5995490..404ca16231 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -18,7 +18,7 @@ use winit::{ use crate::{ accessibility::{AccessKitAdapters, WinitActionHandler, WinitActionHandlers}, - converters::convert_window_level, + converters::{convert_window_level, convert_window_theme}, }; /// A resource which maps window entities to [`winit`] library windows. @@ -92,6 +92,7 @@ impl WinitWindows { winit_window_builder = winit_window_builder .with_window_level(convert_window_level(window.window_level)) + .with_theme(window.window_theme.map(convert_window_theme)) .with_resizable(window.resizable) .with_decorations(window.decorations) .with_transparent(window.transparent); diff --git a/examples/window/window_settings.rs b/examples/window/window_settings.rs index fa9ff806f8..6bbb48aa25 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -4,7 +4,7 @@ use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, - window::{CursorGrabMode, PresentMode, WindowLevel}, + window::{CursorGrabMode, PresentMode, WindowLevel, WindowTheme}, }; fn main() { @@ -18,6 +18,7 @@ fn main() { fit_canvas_to_parent: true, // Tells wasm not to override default event handling, like F5, Ctrl+R etc. prevent_default_event_handling: false, + window_theme: Some(WindowTheme::Dark), ..default() }), ..default() @@ -28,6 +29,7 @@ fn main() { Update, ( change_title, + toggle_theme, toggle_cursor, toggle_vsync, cycle_cursor_icon, @@ -93,6 +95,20 @@ fn toggle_cursor(mut windows: Query<&mut Window>, input: Res>) { } } +// This system will toggle the color theme used by the window +fn toggle_theme(mut windows: Query<&mut Window>, input: Res>) { + if input.just_pressed(KeyCode::F) { + let mut window = windows.single_mut(); + + if let Some(current_theme) = window.window_theme { + window.window_theme = match current_theme { + WindowTheme::Light => Some(WindowTheme::Dark), + WindowTheme::Dark => Some(WindowTheme::Light), + }; + } + } +} + /// This system cycles the cursor's icon through a small set of icons when clicking fn cycle_cursor_icon( mut windows: Query<&mut Window>,