Support to set window theme and expose system window theme changed event (#8593)

# 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 <nicopap@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François <mockersf@gmail.com>
This commit is contained in:
张林伟 2023-06-06 05:04:22 +08:00 committed by GitHub
parent 25add57614
commit b72b15465d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 8 deletions

View File

@ -7,6 +7,8 @@ use bevy_reflect::{FromReflect, Reflect};
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use crate::WindowTheme;
/// A window event that is sent whenever a window's logical size has changed. /// A window event that is sent whenever a window's logical size has changed.
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] #[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)] #[reflect(Debug, PartialEq)]
@ -289,3 +291,19 @@ pub struct WindowMoved {
/// Where the window moved to in physical pixels. /// Where the window moved to in physical pixels.
pub position: IVec2, 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,
}

View File

@ -84,7 +84,8 @@ impl Plugin for WindowPlugin {
.add_event::<WindowScaleFactorChanged>() .add_event::<WindowScaleFactorChanged>()
.add_event::<WindowBackendScaleFactorChanged>() .add_event::<WindowBackendScaleFactorChanged>()
.add_event::<FileDragAndDrop>() .add_event::<FileDragAndDrop>()
.add_event::<WindowMoved>(); .add_event::<WindowMoved>()
.add_event::<WindowThemeChanged>();
if let Some(primary_window) = &self.primary_window { if let Some(primary_window) = &self.primary_window {
app.world app.world
@ -121,7 +122,8 @@ impl Plugin for WindowPlugin {
.register_type::<WindowScaleFactorChanged>() .register_type::<WindowScaleFactorChanged>()
.register_type::<WindowBackendScaleFactorChanged>() .register_type::<WindowBackendScaleFactorChanged>()
.register_type::<FileDragAndDrop>() .register_type::<FileDragAndDrop>()
.register_type::<WindowMoved>(); .register_type::<WindowMoved>()
.register_type::<WindowThemeChanged>();
// Register window descriptor and related types // Register window descriptor and related types
app.register_type::<Window>() app.register_type::<Window>()
@ -136,7 +138,8 @@ impl Plugin for WindowPlugin {
.register_type::<PresentMode>() .register_type::<PresentMode>()
.register_type::<InternalWindowState>() .register_type::<InternalWindowState>()
.register_type::<MonitorSelection>() .register_type::<MonitorSelection>()
.register_type::<WindowResizeConstraints>(); .register_type::<WindowResizeConstraints>()
.register_type::<WindowTheme>();
// Register `PathBuf` as it's used by `FileDragAndDrop` // Register `PathBuf` as it's used by `FileDragAndDrop`
app.register_type::<PathBuf>(); app.register_type::<PathBuf>();

View File

@ -184,6 +184,14 @@ pub struct Window {
/// ///
/// - iOS / Android / Web: Unsupported. /// - iOS / Android / Web: Unsupported.
pub ime_position: Vec2, 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<WindowTheme>,
} }
impl Default for Window { impl Default for Window {
@ -208,6 +216,7 @@ impl Default for Window {
fit_canvas_to_parent: false, fit_canvas_to_parent: false,
prevent_default_event_handling: true, prevent_default_event_handling: true,
canvas: None, canvas: None,
window_theme: None,
} }
} }
} }
@ -832,3 +841,19 @@ pub enum WindowLevel {
/// The window will always be on top of normal windows. /// The window will always be on top of normal windows.
AlwaysOnTop, 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,
}

View File

@ -5,7 +5,7 @@ use bevy_input::{
ButtonState, ButtonState,
}; };
use bevy_math::Vec2; 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 { pub fn convert_keyboard_input(keyboard_input: &winit::event::KeyboardInput) -> KeyboardInput {
KeyboardInput { KeyboardInput {
@ -274,3 +274,17 @@ pub fn convert_window_level(window_level: WindowLevel) -> winit::window::WindowL
WindowLevel::AlwaysOnTop => winit::window::WindowLevel::AlwaysOnTop, 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,
}
}

View File

@ -41,7 +41,7 @@ use bevy_window::{
exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime,
ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged,
WindowCloseRequested, WindowCreated, WindowFocused, WindowMoved, WindowResized, WindowCloseRequested, WindowCreated, WindowFocused, WindowMoved, WindowResized,
WindowScaleFactorChanged, WindowScaleFactorChanged, WindowThemeChanged,
}; };
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@ -54,6 +54,7 @@ use winit::{
use crate::accessibility::{AccessKitAdapters, AccessibilityPlugin, WinitActionHandlers}; use crate::accessibility::{AccessKitAdapters, AccessibilityPlugin, WinitActionHandlers};
use crate::converters::convert_winit_theme;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin}; use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin};
@ -227,6 +228,7 @@ struct WindowEvents<'w> {
window_backend_scale_factor_changed: EventWriter<'w, WindowBackendScaleFactorChanged>, window_backend_scale_factor_changed: EventWriter<'w, WindowBackendScaleFactorChanged>,
window_focused: EventWriter<'w, WindowFocused>, window_focused: EventWriter<'w, WindowFocused>,
window_moved: EventWriter<'w, WindowMoved>, window_moved: EventWriter<'w, WindowMoved>,
window_theme_changed: EventWriter<'w, WindowThemeChanged>,
} }
#[derive(SystemParam)] #[derive(SystemParam)]
@ -613,6 +615,12 @@ pub fn winit_runner(mut app: App) {
window: window_entity, window: window_entity,
}), }),
}, },
WindowEvent::ThemeChanged(theme) => {
window_events.window_theme_changed.send(WindowThemeChanged {
window: window_entity,
theme: convert_winit_theme(theme),
});
}
_ => {} _ => {}
} }

View File

@ -23,7 +23,7 @@ use winit::{
use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR}; use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR};
use crate::{ use crate::{
accessibility::{AccessKitAdapters, WinitActionHandlers}, 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, get_best_videomode, get_fitting_videomode, WinitWindows,
}; };
@ -62,6 +62,11 @@ pub(crate) fn create_window<'a>(
&mut handlers, &mut handlers,
&mut accessibility_requested, &mut accessibility_requested,
); );
if let Some(theme) = winit_window.theme() {
window.window_theme = Some(convert_winit_theme(theme));
}
window window
.resolution .resolution
.set_scale_factor(winit_window.scale_factor()); .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(); cache.window = window.clone();
} }
} }

View File

@ -18,7 +18,7 @@ use winit::{
use crate::{ use crate::{
accessibility::{AccessKitAdapters, WinitActionHandler, WinitActionHandlers}, 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. /// A resource which maps window entities to [`winit`] library windows.
@ -92,6 +92,7 @@ impl WinitWindows {
winit_window_builder = winit_window_builder winit_window_builder = winit_window_builder
.with_window_level(convert_window_level(window.window_level)) .with_window_level(convert_window_level(window.window_level))
.with_theme(window.window_theme.map(convert_window_theme))
.with_resizable(window.resizable) .with_resizable(window.resizable)
.with_decorations(window.decorations) .with_decorations(window.decorations)
.with_transparent(window.transparent); .with_transparent(window.transparent);

View File

@ -4,7 +4,7 @@
use bevy::{ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*, prelude::*,
window::{CursorGrabMode, PresentMode, WindowLevel}, window::{CursorGrabMode, PresentMode, WindowLevel, WindowTheme},
}; };
fn main() { fn main() {
@ -18,6 +18,7 @@ fn main() {
fit_canvas_to_parent: true, fit_canvas_to_parent: true,
// Tells wasm not to override default event handling, like F5, Ctrl+R etc. // Tells wasm not to override default event handling, like F5, Ctrl+R etc.
prevent_default_event_handling: false, prevent_default_event_handling: false,
window_theme: Some(WindowTheme::Dark),
..default() ..default()
}), }),
..default() ..default()
@ -28,6 +29,7 @@ fn main() {
Update, Update,
( (
change_title, change_title,
toggle_theme,
toggle_cursor, toggle_cursor,
toggle_vsync, toggle_vsync,
cycle_cursor_icon, cycle_cursor_icon,
@ -93,6 +95,20 @@ fn toggle_cursor(mut windows: Query<&mut Window>, input: Res<Input<KeyCode>>) {
} }
} }
// This system will toggle the color theme used by the window
fn toggle_theme(mut windows: Query<&mut Window>, input: Res<Input<KeyCode>>) {
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 /// This system cycles the cursor's icon through a small set of icons when clicking
fn cycle_cursor_icon( fn cycle_cursor_icon(
mut windows: Query<&mut Window>, mut windows: Query<&mut Window>,