Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c750050
New `bevy_preferences` crate
viridia Feb 18, 2026
d3fa501
CI
viridia Feb 18, 2026
02b7b12
Doc fixes.
viridia Feb 18, 2026
a7c3bf9
Typo
viridia Feb 18, 2026
ce2fbd4
Changed PreferencesGroup annotation to derive macro.
viridia Feb 20, 2026
004bf2e
Moved case conversion to a better place.
viridia Feb 20, 2026
2f1cf3a
Implement override of group name.
viridia Feb 20, 2026
d70e03d
Section merging.
viridia Feb 21, 2026
f8e42e0
Remove dependency on Serialize/Deserialize.
viridia Feb 21, 2026
71cdf5b
Window position saving.
viridia Feb 22, 2026
34f6649
Changed `PreferencesFile` annotation to derive attribute.
viridia Feb 22, 2026
203b6a5
Added preferences save timer.
viridia Feb 22, 2026
8535863
CI
viridia Feb 22, 2026
560b390
CI
viridia Feb 22, 2026
b477c22
CI
viridia Feb 22, 2026
2083c23
Renamed bevy_preferences to bevy_settings.
viridia Feb 23, 2026
19677f5
Documentation and release note.
viridia Feb 24, 2026
f756f46
More tests.
viridia Feb 24, 2026
c561545
Removed `load_preferences` method.
viridia Feb 24, 2026
a92a338
CI
viridia Feb 24, 2026
413b686
CI
viridia Feb 24, 2026
ff49a43
Doc polish.
viridia Mar 5, 2026
0abe75f
Review feedback.
viridia Mar 6, 2026
25a5c62
Use module docs from kfc35
alice-i-cecile Mar 10, 2026
9d6acca
Improved module docs for prefs_windows
alice-i-cecile Mar 10, 2026
ca25fb6
Update crates/bevy_settings/src/lib.rs
viridia Mar 10, 2026
7ebb441
Apply suggestions from code review
viridia Mar 10, 2026
77d1eb7
Review feedback.
viridia Mar 10, 2026
c6215ad
Merge branch 'main' into bevy_preferences
viridia Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,9 @@ bevy_input_focus = ["bevy_internal/bevy_input_focus"]
# Headless widget collection for Bevy UI.
bevy_ui_widgets = ["bevy_internal/bevy_ui_widgets"]

# Load and save user preferences
bevy_settings = ["bevy_internal/bevy_settings"]

# Feathers widget collection.
experimental_bevy_feathers = ["bevy_internal/bevy_feathers", "bevy_ui_widgets"]

Expand Down Expand Up @@ -5201,6 +5204,30 @@ description = "Demonstrates use of core scrollbar in Bevy UI"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "persisting_preferences"
path = "examples/app/persisting_preferences.rs"
doc-scrape-examples = true
required-features = ["bevy_settings"]

[package.metadata.example.persisting_preferences]
name = "User Preferences"
description = "Demonstrates persistence of user preferences"
category = "Application"
wasm = true

[[example]]
name = "persisting_window_settings"
path = "examples/window/persisting_window_settings.rs"
doc-scrape-examples = true
required-features = ["bevy_settings"]

[package.metadata.example.persisting_window_settings]
name = "Save Window Position"
description = "Demonstrates saving window position in preferences"
category = "Application"
wasm = false

[[example]]
name = "system_fonts"
path = "examples/ui/text/system_fonts.rs"
Expand Down
60 changes: 59 additions & 1 deletion crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use crate::{
component::map_entities, query_data::derive_query_data_impl,
query_filter::derive_query_filter_impl,
};
use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
use bevy_macro_utils::{
derive_label, ensure_no_collision, get_struct_fields, pascal_to_snake_case, BevyManifest,
};
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{format_ident, quote, ToTokens};
Expand Down Expand Up @@ -567,6 +569,62 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
component::derive_resource(input)
}

/// Implement the `SettingsGroup` trait.
#[proc_macro_derive(SettingsGroup, attributes(settings_group))]
pub fn derive_settings_group(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

let name = &input.ident;

let (override_name, override_file) = {
let mut override_name: Option<String> = None;
let mut override_file: Option<String> = None;

input
.attrs
.iter()
.find(|attr| attr.path().is_ident("settings_group"))
.and_then(|attr| {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("group") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
override_name = Some(s.value());
Ok(())
} else if meta.path.is_ident("file") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
override_file = Some(s.value());
Ok(())
} else {
Err(meta.error("unsupported attribute"))
}
})
.ok()
});

(override_name, override_file)
};

let group_name = override_name.unwrap_or(pascal_to_snake_case(&name.to_string()));
let file_name = override_file
.map(|f| quote! { Some(#f) })
.unwrap_or(quote! { None });

let expanded = quote! {
impl SettingsGroup for #name {
fn settings_group_name() -> &'static str {
#group_name
}
fn settings_source() -> Option<&'static str> {
#file_name
}
}
};

TokenStream::from(expanded)
}

/// Cheat sheet for derive syntax,
/// see full explanation and examples on the `Component` trait doc.
///
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0
] }
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.19.0-dev" }
bevy_picking = { path = "../bevy_picking", optional = true, version = "0.19.0-dev" }
bevy_settings = { path = "../bevy_settings", optional = true, version = "0.19.0-dev" }
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.19.0-dev" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.19.0-dev" }
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.19.0-dev" }
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ pub use bevy_remote as remote;
pub use bevy_render as render;
#[cfg(feature = "bevy_scene")]
pub use bevy_scene as scene;
#[cfg(feature = "bevy_settings")]
pub use bevy_settings as settings;
#[cfg(feature = "bevy_shader")]
pub use bevy_shader as shader;
#[cfg(feature = "bevy_solari")]
Expand Down
33 changes: 33 additions & 0 deletions crates/bevy_macro_utils/src/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,36 @@ pub fn derive_label(
}
.into()
}

/// Convert a string from ``PascalCase`` to ``snake_case``.
pub fn pascal_to_snake_case(s: &str) -> String {
let mut out = String::new();
let chars: Vec<char> = s.chars().collect();

for (i, &ch) in chars.iter().enumerate() {
if ch.is_uppercase() {
let prev_is_lower = i > 0 && chars[i - 1].is_lowercase();
let next_is_lower = chars.get(i + 1).is_some_and(|c| c.is_lowercase());

if i > 0 && (prev_is_lower || next_is_lower) {
out.push('_');
}
out.push(ch.to_lowercase().next().unwrap());
} else {
out.push(ch);
}
}

out
}
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_pascal_to_snake_case() {
assert_eq!(pascal_to_snake_case("PascalCase"), "pascal_case");
assert_eq!(pascal_to_snake_case("lowercase"), "lowercase");
assert_eq!(pascal_to_snake_case("HTTPServer"), "http_server");
}
}
40 changes: 40 additions & 0 deletions crates/bevy_settings/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[package]
name = "bevy_settings"
version = "0.19.0-dev"
edition = "2024"
description = "User settings framework for Bevy Engine"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.19.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" }
bevy_ecs_macros = { path = "../bevy_ecs/macros", version = "0.19.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.19.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.19.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.19.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.19.0-dev" }

serde = "1.0.217"
thiserror = "2.0.18"
toml = { version = "0.8.19" }

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", default-features = false, features = [
"Window",
"Storage",
] }

[features]
default = []

[lints]
workspace = true

[package.metadata.docs.rs]
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
all-features = true
Loading