From 8196b28b239dbecccbc80c6a7ef71e68f5237cef Mon Sep 17 00:00:00 2001 From: Cyannide Date: Thu, 8 May 2025 20:57:42 -0500 Subject: [PATCH 1/3] Updated to Bevy 0.15. --- Cargo.toml | 4 +- README.md | 1 + examples/autosize.rs | 63 +++++++++--------- examples/buttons.rs | 92 +++++++++++++-------------- examples/circle.rs | 26 ++++---- examples/compare.rs | 73 +++++++++------------ examples/shapes.rs | 59 ++++++++--------- examples/simple.rs | 26 ++++---- examples/superellipse.rs | 47 ++++++-------- examples/superellipse_transparency.rs | 59 +++++++---------- 10 files changed, 195 insertions(+), 255 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e8fd058..10407ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_round_ui" -version = "2.0.0" +version = "4.0.0" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Robert Dodd"] @@ -23,4 +23,4 @@ round_rect = [] superellipse = [] [dependencies] -bevy = "0.14" +bevy = "0.15" diff --git a/README.md b/README.md index 478916c..d1acb17 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ allows you to toggle between the superellipse and round-rect materials to easily | `bevy_round_ui` | `bevy` | |:----------------|:-------| +| `4.x` | `0.15` | | `1.x` - `2.x` | `0.13` | | `0.2` | `0.13` | | `0.1` | `0.12` | diff --git a/examples/autosize.rs b/examples/autosize.rs index 471c504..1638542 100644 --- a/examples/autosize.rs +++ b/examples/autosize.rs @@ -13,29 +13,20 @@ fn main() { fn setup(mut commands: Commands, mut materials: ResMut>) { // Camera so we can see UI - commands.spawn(Camera2dBundle::default()); + commands.spawn(Camera2d); // Spawn two nested panels with flexible sizes in the middle of the window commands - .spawn(NodeBundle { - style: Style { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - ..default() - }, + .spawn((Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, ..default() - }) + },)) .with_children(|p| { - p.spawn(MaterialNodeBundle { - material: materials.add(RoundRectUiMaterial { - background_color: css::PINK.into(), - border_color: LinearRgba::WHITE, - border_radius: RoundUiBorder::all(20.).into(), - offset: RoundUiOffset::all(6.).into(), - }), - style: Style { + p.spawn(( + Node { width: Val::Percent(50.), height: Val::Percent(50.), align_items: AlignItems::Center, @@ -44,17 +35,16 @@ fn setup(mut commands: Commands, mut materials: ResMut>, ) { // Camera so we can see UI - commands.spawn(Camera2dBundle::default()); + commands.spawn(Camera2d); // Define a material for the panel. // This material looks like it has a border, because we applied an equal offset to all sides. @@ -98,20 +98,17 @@ fn setup( // Spawn the screen layout, containing a centered panel with menu items commands - .spawn(NodeBundle { - style: Style { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - ..default() - }, + .spawn((Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, ..default() - }) + },)) .with_children(|p| { - p.spawn(MaterialNodeBundle { - material: panel_material, - style: Style { + p.spawn(( + MaterialNode(panel_material), + Node { width: Val::Px(PANEL_WIDTH), padding: UiRect::axes(Val::Px(40.), Val::Px(60.)), flex_direction: FlexDirection::Column, @@ -119,29 +116,25 @@ fn setup( justify_content: JustifyContent::Center, ..default() }, - ..default() - }) + )) .with_children(|p| { // Spawn the title - p.spawn(NodeBundle { - style: Style { - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - margin: UiRect::bottom(Val::Px(40.)), - ..default() - }, + p.spawn((Node { + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + margin: UiRect::bottom(Val::Px(40.)), ..default() - }) - .with_children(|p| { - p.spawn(TextBundle::from_section( - "MENU", - TextStyle { - color: Color::WHITE, - font_size: 40., - ..default() - }, - )); - }); + },)) + .with_children(|p| { + p.spawn(( + Text::new("MENU"), + TextFont { + font_size: 40., + ..default() + }, + TextColor::WHITE, + )); + }); // Spawn the buttons spawn_button(p, &button_style, "Play", ButtonAction::Play); @@ -161,29 +154,26 @@ fn spawn_button( parent .spawn(( RoundButton, - MaterialNodeBundle { - material: button_style.default_material.clone(), - style: Style { - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - width: Val::Percent(100.), - height: Val::Px(BUTTON_HEIGHT), - margin: UiRect::top(Val::Px(10.)), - ..default() - }, + Node { + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + width: Val::Percent(100.), + height: Val::Px(BUTTON_HEIGHT), + margin: UiRect::top(Val::Px(10.)), ..default() }, + MaterialNode(button_style.default_material.clone()), extras, Interaction::default(), )) .with_children(|p| { - p.spawn(TextBundle::from_section( - text, - TextStyle { - color: Color::WHITE, + p.spawn(( + Text::new(text), + TextFont { font_size: 20., ..default() }, + TextColor::WHITE, )); }) .id() @@ -193,7 +183,11 @@ fn spawn_button( #[allow(clippy::type_complexity)] fn handle_button_interactions( mut interaction_query: Query< - (&Interaction, &mut Handle, &mut Style), + ( + &Interaction, + &mut MaterialNode, + &mut Node, + ), (Changed, With), >, button_style: Res, @@ -213,7 +207,7 @@ fn handle_button_interactions( button_style.default_padding, ), }; - *material = material_handle; + **material = material_handle; style.padding = padding; } } diff --git a/examples/circle.rs b/examples/circle.rs index 660d557..761cf68 100644 --- a/examples/circle.rs +++ b/examples/circle.rs @@ -20,7 +20,7 @@ const CIRCLE_BORDER_COLOR: &str = "#A53A3D"; fn setup(mut commands: Commands, mut materials: ResMut>) { // Camera so we can see UI - commands.spawn(Camera2dBundle::default()); + commands.spawn(Camera2d); // Add the material asset. // NOTE: To make a circle, the width, height and border radius should be equal @@ -33,25 +33,21 @@ fn setup(mut commands: Commands, mut materials: ResMut>, ) { // Camera so we can see UI - commands.spawn(Camera2dBundle::default()); + commands.spawn(Camera2d); let border_radius: Vec4 = RoundUiBorder::all(PANEL_WIDTH / 4.).into(); let background_color: LinearRgba = Srgba::hex("#F76161").unwrap().into(); @@ -55,13 +55,10 @@ fn setup( // Spawn help text commands - .spawn(NodeBundle { - style: Style { - flex_direction: FlexDirection::Column, - ..default() - }, + .spawn((Node { + flex_direction: FlexDirection::Column, ..default() - }) + },)) .with_children(|p| { help_text(p, "Toggle Material: ENTER"); help_text(p, "Toggle Height: SPACE"); @@ -70,67 +67,55 @@ fn setup( // Spawn the material in the middle of the screen commands - .spawn(NodeBundle { - style: Style { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - ..default() - }, + .spawn((Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, ..default() - }) + },)) .with_children(|p| { // Spawn a wrapper around the material node with a blue border p.spawn(( PanelBorder(true), - NodeBundle { - style: Style { - border: UiRect::all(Val::Px(1.)), - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - ..default() - }, - border_color: css::BLUE.into(), + Node { + border: UiRect::all(Val::Px(1.)), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, ..default() }, + BorderColor(css::BLUE.into()), )) .with_children(|p| { // Spawn a superellipse material, displayed by default p.spawn(( PanelSize::Short, - MaterialNodeBundle { - material: panel_material_superellipse, - style: Style { - width: Val::Px(PANEL_WIDTH), - height: Val::Px(PANEL_HEIGHT), - ..default() - }, + Node { + width: Val::Px(PANEL_WIDTH), + height: Val::Px(PANEL_HEIGHT), ..default() }, + MaterialNode(panel_material_superellipse), )); // Spawn a round rect material, hidden by default p.spawn(( PanelSize::Short, - MaterialNodeBundle { - material: panel_material_round_rect, - style: Style { - width: Val::Px(PANEL_WIDTH), - height: Val::Px(PANEL_HEIGHT), - display: Display::None, - ..default() - }, + Node { + width: Val::Px(PANEL_WIDTH), + height: Val::Px(PANEL_HEIGHT), + display: Display::None, ..default() }, + MaterialNode(panel_material_round_rect), )); }); }); } fn help_text(p: &mut ChildBuilder, val: impl Into) { - p.spawn(TextBundle::from_section( - val, - TextStyle { + p.spawn(( + Text::new(val), + TextFont { font_size: 24., ..default() }, @@ -139,8 +124,8 @@ fn help_text(p: &mut ChildBuilder, val: impl Into) { fn handle_keys( keys: Res>, - mut query: Query<(&mut PanelSize, &mut Style)>, - mut border_query: Query<(&mut PanelBorder, &mut Style), Without>, + mut query: Query<(&mut PanelSize, &mut Node)>, + mut border_query: Query<(&mut PanelBorder, &mut Node), Without>, ) { // Change height when "Space" is pressed if keys.just_pressed(KeyCode::Space) { diff --git a/examples/shapes.rs b/examples/shapes.rs index 02130f4..f6a2dba 100644 --- a/examples/shapes.rs +++ b/examples/shapes.rs @@ -17,7 +17,7 @@ const SPACER_SIZE: f32 = 20.; fn setup(mut commands: Commands, mut materials: ResMut>) { // Camera so we can see UI - commands.spawn(Camera2dBundle::default()); + commands.spawn(Camera2d); let rect_materials = [ // Round rect without offset @@ -81,42 +81,35 @@ fn setup(mut commands: Commands, mut materials: ResMut>) { // Camera so we can see UI - commands.spawn(Camera2dBundle::default()); + commands.spawn(Camera2d); // Add the material let panel_material = materials.add(RoundRectUiMaterial { @@ -28,25 +28,21 @@ fn setup(mut commands: Commands, mut materials: ResMut>) { // Camera so we can see UI - commands.spawn(Camera2dBundle::default()); + commands.spawn(Camera2d); let border_radius: Vec4 = RoundUiBorder::all(PANEL_WIDTH / 4.).into(); let background_color: LinearRgba = Srgba::hex("#5cb3af").unwrap().into(); @@ -41,57 +41,48 @@ fn setup(mut commands: Commands, mut materials: ResMut) { - p.spawn(TextBundle::from_section( - val, - TextStyle { + p.spawn(( + Text::new(val), + TextFont { font_size: 24., ..default() }, )); } -fn handle_keys(keys: Res>, mut query: Query<(&mut PanelSize, &mut Style)>) { +fn handle_keys(keys: Res>, mut query: Query<(&mut PanelSize, &mut Node)>) { if keys.just_pressed(KeyCode::Space) { for (mut panel_size, mut style) in query.iter_mut() { *panel_size = match *panel_size { diff --git a/examples/superellipse_transparency.rs b/examples/superellipse_transparency.rs index 95561f7..7169eea 100644 --- a/examples/superellipse_transparency.rs +++ b/examples/superellipse_transparency.rs @@ -24,7 +24,7 @@ pub enum PanelSize { fn setup(mut commands: Commands, mut materials: ResMut>) { // Camera so we can see UI - commands.spawn(Camera2dBundle::default()); + commands.spawn(Camera2d); let border_radius: Vec4 = RoundUiBorder::all(PANEL_WIDTH / 4.).into(); let background_color: LinearRgba = Color::srgba(0.36078432, 0.7019608, 0.6862745, 0.5).into(); @@ -40,62 +40,51 @@ fn setup(mut commands: Commands, mut materials: ResMut Date: Fri, 9 May 2025 15:47:37 -0500 Subject: [PATCH 2/3] Adjusted shaders to account for the ComputedNode inverse scale factor. --- examples/autosize.rs | 2 ++ examples/buttons.rs | 4 ++++ examples/circle.rs | 1 + examples/compare.rs | 2 ++ examples/shapes.rs | 6 ++++++ examples/simple.rs | 1 + examples/superellipse.rs | 1 + examples/superellipse_transparency.rs | 1 + src/round_rect.rs | 19 ++++++++++++++++++- src/round_rect.wgsl | 14 ++++++++------ src/superellipse.rs | 19 ++++++++++++++++++- src/superellipse.wgsl | 8 +++++--- 12 files changed, 67 insertions(+), 11 deletions(-) diff --git a/examples/autosize.rs b/examples/autosize.rs index 1638542..563abda 100644 --- a/examples/autosize.rs +++ b/examples/autosize.rs @@ -40,6 +40,7 @@ fn setup(mut commands: Commands, mut materials: ResMut::default()); + app.add_plugins(UiMaterialPlugin::::default()) + .add_systems(PreUpdate, update_round_rect_inverse_scale_factor); + } +} + +pub fn update_round_rect_inverse_scale_factor( + query: Query<(&ComputedNode, &MaterialNode)>, + mut materials: ResMut>, +) { + for (computed_node, material) in &query { + if let Some(mat) = materials.get_mut(material) { + mat.inverse_scale_factor = computed_node.inverse_scale_factor(); + } } } @@ -41,6 +53,10 @@ pub struct RoundRectUiMaterial { /// E.g. Vec4::new((top, left, bottom, right) #[uniform(0)] pub offset: Vec4, + + /// The ComputedNode inverse scale factor + #[uniform(0)] + pub inverse_scale_factor: f32, } impl Default for RoundRectUiMaterial { @@ -50,6 +66,7 @@ impl Default for RoundRectUiMaterial { border_color: LinearRgba::NONE, border_radius: Vec4::splat(0.), offset: Vec4::splat(0.), + inverse_scale_factor: 1., } } } diff --git a/src/round_rect.wgsl b/src/round_rect.wgsl index 2940471..267c399 100644 --- a/src/round_rect.wgsl +++ b/src/round_rect.wgsl @@ -8,6 +8,8 @@ struct RoundUiMaterial { @location(2) border_radius: vec4, /// border offset: (top, left, bottom, right) @location(3) offset: vec4, + /// Inverse scale factor: must be updated to match the ComputedNode + @location(4) inverse_scale_factor: f32, } @group(1) @binding(0) @@ -34,24 +36,24 @@ fn fragment(in: UiVertexOutput) -> @location(0) vec4 { // position offset to account for border let border_offset = vec2( - input.offset.w - input.offset.y, // right - left - input.offset.z - input.offset.x, // bottom - top + (input.offset.w - input.offset.y) / input.inverse_scale_factor, // right - left + (input.offset.z - input.offset.x) / input.inverse_scale_factor, // bottom - top ); // SDF distance in the inner button area // The inner button size is equal to actual size - offset size let size = in.size - vec2( - input.offset.y + input.offset.w, // left + right - input.offset.x + input.offset.z, // top + bottom + (input.offset.y + input.offset.w) / input.inverse_scale_factor, // left + right + (input.offset.x + input.offset.z) / input.inverse_scale_factor, // top + bottom ); let d_shape = sdf_rounded_rect( uv + border_offset, size, - input.border_radius, + input.border_radius / input.inverse_scale_factor, ); // SDF distance in border area - let d_border = sdf_rounded_rect(uv, in.size, input.border_radius); + let d_border = sdf_rounded_rect(uv, in.size, input.border_radius / input.inverse_scale_factor); // define the alpha value. Opaque if within the button or border area, // transparent otherwise. diff --git a/src/superellipse.rs b/src/superellipse.rs index e648acc..13cb316 100644 --- a/src/superellipse.rs +++ b/src/superellipse.rs @@ -14,7 +14,19 @@ impl Plugin for SuperellipseMaterialPlugin { Shader::from_wgsl ); - app.add_plugins(UiMaterialPlugin::::default()); + app.add_plugins(UiMaterialPlugin::::default()) + .add_systems(PreUpdate, update_superellipse_inverse_scale_factor); + } +} + +pub fn update_superellipse_inverse_scale_factor( + query: Query<(&ComputedNode, &MaterialNode)>, + mut materials: ResMut>, +) { + for (computed_node, material) in &query { + if let Some(mat) = materials.get_mut(material) { + mat.inverse_scale_factor = computed_node.inverse_scale_factor(); + } } } @@ -44,6 +56,10 @@ pub struct SuperellipseUiMaterial { /// The thickness of the border #[uniform(0)] pub border_thickness: f32, + + /// The ComputedNode inverse scale factor + #[uniform(0)] + pub inverse_scale_factor: f32, } impl Default for SuperellipseUiMaterial { @@ -53,6 +69,7 @@ impl Default for SuperellipseUiMaterial { border_color: LinearRgba::NONE, border_radius: Vec4::splat(0.), border_thickness: 0., + inverse_scale_factor: 1., } } } diff --git a/src/superellipse.wgsl b/src/superellipse.wgsl index e1521b6..103af0d 100644 --- a/src/superellipse.wgsl +++ b/src/superellipse.wgsl @@ -10,6 +10,8 @@ struct SuperellipseUiMaterial { @location(2) border_radius: vec4, /// Border thickness: ignored if `border_color.a == 0.0` @location(3) border_thickness: f32, + /// Inverse scale factor: must be updated to match the ComputedNode + @location(4) inverse_scale_factor: f32, } @group(1) @binding(0) @@ -44,7 +46,7 @@ fn fragment(in: UiVertexOutput) -> @location(0) vec4 { // adjust size by subtracting the border thickness var size = in.size; if is_border { - size -= vec2f(input.border_thickness); + size -= vec2f(input.border_thickness / input.inverse_scale_factor); } // adjust UVs around the middle of the rect, and convert to pixel @@ -57,7 +59,7 @@ fn fragment(in: UiVertexOutput) -> @location(0) vec4 { // IMPORTANT: Minimum border radius of 0.2, otherwise the approximation // behaves strangely. - let border_radius = max(input.border_radius / min_size, vec4f(0.2)); + let border_radius = max(input.border_radius / input.inverse_scale_factor / min_size, vec4f(0.2)); // Compute signed distance let d = approx_sd_super_ellipse( @@ -77,7 +79,7 @@ fn fragment(in: UiVertexOutput) -> @location(0) vec4 { // Apply border color var result = vec4f(col, alpha); if is_border { - let border_thickness_uv = input.border_thickness / min_size; + let border_thickness_uv = input.border_thickness / input.inverse_scale_factor / min_size; result = mix(result, input.border_color, 1.0 - smoothstep(0.0, border_thickness_uv, abs(d))); } From ff81260ccb85c9f51c86862ca212d7ba06a2d22f Mon Sep 17 00:00:00 2001 From: Cyannide Date: Fri, 9 May 2025 19:48:38 -0500 Subject: [PATCH 3/3] Updated to Bevy 0.16. --- Cargo.toml | 4 ++-- README.md | 1 + examples/buttons.rs | 4 ++-- examples/compare.rs | 2 +- examples/superellipse.rs | 2 +- src/round_rect.rs | 9 +++++++-- src/superellipse.rs | 9 +++++++-- 7 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 10407ad..e4e39d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_round_ui" -version = "4.0.0" +version = "5.0.0" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Robert Dodd"] @@ -23,4 +23,4 @@ round_rect = [] superellipse = [] [dependencies] -bevy = "0.15" +bevy = "0.16" diff --git a/README.md b/README.md index d1acb17..f3e9b71 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ allows you to toggle between the superellipse and round-rect materials to easily | `bevy_round_ui` | `bevy` | |:----------------|:-------| +| `5.x` | `0.16` | | `4.x` | `0.15` | | `1.x` - `2.x` | `0.13` | | `0.2` | `0.13` | diff --git a/examples/buttons.rs b/examples/buttons.rs index 961ccdd..ab05ba3 100644 --- a/examples/buttons.rs +++ b/examples/buttons.rs @@ -150,7 +150,7 @@ fn setup( /// Utility that spawns a new button. fn spawn_button( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, button_style: &ButtonStyle, text: impl Into, extras: impl Bundle, @@ -228,7 +228,7 @@ fn handle_button_actions( ButtonAction::Play => (), ButtonAction::Settings => (), ButtonAction::Quit => { - app_exit_events.send(AppExit::Success); + app_exit_events.write(AppExit::Success); } } } diff --git a/examples/compare.rs b/examples/compare.rs index d044d7e..846497f 100644 --- a/examples/compare.rs +++ b/examples/compare.rs @@ -114,7 +114,7 @@ fn setup( }); } -fn help_text(p: &mut ChildBuilder, val: impl Into) { +fn help_text(p: &mut ChildSpawnerCommands, val: impl Into) { p.spawn(( Text::new(val), TextFont { diff --git a/examples/superellipse.rs b/examples/superellipse.rs index bb47611..da8ff5e 100644 --- a/examples/superellipse.rs +++ b/examples/superellipse.rs @@ -73,7 +73,7 @@ fn setup(mut commands: Commands, mut materials: ResMut) { +fn help_text(p: &mut ChildSpawnerCommands, val: impl Into) { p.spawn(( Text::new(val), TextFont { diff --git a/src/round_rect.rs b/src/round_rect.rs index 6e382a1..aaae9e0 100644 --- a/src/round_rect.rs +++ b/src/round_rect.rs @@ -1,8 +1,13 @@ -use bevy::{asset::load_internal_asset, prelude::*, render::render_resource::*}; +use bevy::{ + asset::{load_internal_asset, weak_handle}, + prelude::*, + render::render_resource::*, +}; use crate::types::*; -pub const ROUND_RECT_SHADER_HANDLE: Handle = Handle::weak_from_u128(66552904175742639684); +#[rustfmt::skip] +pub const ROUND_RECT_SHADER_HANDLE: Handle = weak_handle!("0196b79a-6b39-71f0-a57f-87912ea368a9"); /// Plugin which adds a `RoundRectUiMaterial` to the app. pub struct RoundRectMaterialPlugin; diff --git a/src/superellipse.rs b/src/superellipse.rs index 13cb316..8e4f307 100644 --- a/src/superellipse.rs +++ b/src/superellipse.rs @@ -1,6 +1,11 @@ -use bevy::{asset::load_internal_asset, prelude::*, render::render_resource::*}; +use bevy::{ + asset::{load_internal_asset, weak_handle}, + prelude::*, + render::render_resource::*, +}; -pub const SUPERELLIPSE_SHADER_HANDLE: Handle = Handle::weak_from_u128(84071151984186645753); +#[rustfmt::skip] +pub const SUPERELLIPSE_SHADER_HANDLE: Handle = weak_handle!("0196b79a-31a5-7a86-9a00-d1f63e0982f0"); /// Plugin which adds a `SuperellipseUiMaterial` to the app. pub struct SuperellipseMaterialPlugin;