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:
parent
25add57614
commit
b72b15465d
@ -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,
|
||||||
|
}
|
||||||
|
@ -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>();
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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),
|
||||||
|
});
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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>,
|
||||||
|
Loading…
Reference in New Issue
Block a user