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")]
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,
}

View file

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

View file

@ -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<WindowTheme>,
}
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,
}

View file

@ -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,
}
}

View file

@ -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),
});
}
_ => {}
}

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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<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
fn cycle_cursor_icon(
mut windows: Query<&mut Window>,