mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Decouple BackgroundColor
from UiImage
(#11165)
# Objective Fixes https://github.com/bevyengine/bevy/issues/11157. ## Solution Stop using `BackgroundColor` as a color tint for `UiImage`. Add a `UiImage::color` field for color tint instead. Allow a UI node to simultaneously include a solid-color background and an image, with the image rendered on top of the background (this is already how it works for e.g. text). ![2024-02-29_1709239666_563x520](https://github.com/bevyengine/bevy/assets/12173779/ec50c9ef-4c7f-4ab8-a457-d086ce5b3425) --- ## Changelog - The `BackgroundColor` component now renders a solid-color background behind `UiImage` instead of tinting its color. - Removed `BackgroundColor` from `ImageBundle`, `AtlasImageBundle`, and `ButtonBundle`. - Added `UiImage::color`. - Expanded `RenderUiSystem` variants. - Renamed `bevy_ui::extract_text_uinodes` to `extract_uinodes_text` for consistency. ## Migration Guide - `BackgroundColor` no longer tints the color of UI images. Use `UiImage::color` for that instead. - For solid color buttons, replace `ButtonBundle { background_color: my_color.into(), ... }` with `ButtonBundle { image: UiImage::default().with_color(my_color), ... }`, and update button interaction systems to use `UiImage::color` instead of `BackgroundColor` as well. - `bevy_ui::RenderUiSystem::ExtractNode` has been split into `ExtractBackgrounds`, `ExtractImages`, `ExtractBorders`, and `ExtractText`. - `bevy_ui::extract_uinodes` has been split into `bevy_ui::extract_uinode_background_colors` and `bevy_ui::extract_uinode_images`. - `bevy_ui::extract_text_uinodes` has been renamed to `extract_uinode_text`.
This commit is contained in:
parent
6e83439a06
commit
e8ae0d6c49
16 changed files with 241 additions and 214 deletions
|
@ -93,10 +93,6 @@ pub struct ImageBundle {
|
|||
pub style: Style,
|
||||
/// The calculated size based on the given image
|
||||
pub calculated_size: ContentSize,
|
||||
/// The background color, which serves as a "fill" for this node
|
||||
///
|
||||
/// Combines with `UiImage` to tint the provided image.
|
||||
pub background_color: BackgroundColor,
|
||||
/// The image of the node
|
||||
pub image: UiImage,
|
||||
/// The size of the image in pixels
|
||||
|
@ -140,10 +136,6 @@ pub struct AtlasImageBundle {
|
|||
pub style: Style,
|
||||
/// The calculated size based on the given image
|
||||
pub calculated_size: ContentSize,
|
||||
/// The background color, which serves as a "fill" for this node
|
||||
///
|
||||
/// Combines with `UiImage` to tint the provided image.
|
||||
pub background_color: BackgroundColor,
|
||||
/// The image of the node
|
||||
pub image: UiImage,
|
||||
/// A handle to the texture atlas to use for this Ui Node
|
||||
|
@ -319,10 +311,6 @@ pub struct ButtonBundle {
|
|||
pub interaction: Interaction,
|
||||
/// Whether this node should block interaction with lower nodes
|
||||
pub focus_policy: FocusPolicy,
|
||||
/// The background color, which serves as a "fill" for this node
|
||||
///
|
||||
/// When combined with `UiImage`, tints the provided image.
|
||||
pub background_color: BackgroundColor,
|
||||
/// The color of the Node's border
|
||||
pub border_color: BorderColor,
|
||||
/// The image of the node
|
||||
|
@ -349,13 +337,12 @@ pub struct ButtonBundle {
|
|||
impl Default for ButtonBundle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
focus_policy: FocusPolicy::Block,
|
||||
node: Default::default(),
|
||||
button: Default::default(),
|
||||
style: Default::default(),
|
||||
border_color: BorderColor(Color::NONE),
|
||||
interaction: Default::default(),
|
||||
background_color: Default::default(),
|
||||
focus_policy: FocusPolicy::Block,
|
||||
border_color: BorderColor(Color::NONE),
|
||||
image: Default::default(),
|
||||
transform: Default::default(),
|
||||
global_transform: Default::default(),
|
||||
|
|
|
@ -59,7 +59,10 @@ pub const UI_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(130128470471
|
|||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub enum RenderUiSystem {
|
||||
ExtractNode,
|
||||
ExtractBackgrounds,
|
||||
ExtractImages,
|
||||
ExtractBorders,
|
||||
ExtractText,
|
||||
}
|
||||
|
||||
pub fn build_ui_render(app: &mut App) {
|
||||
|
@ -77,16 +80,27 @@ pub fn build_ui_render(app: &mut App) {
|
|||
.allow_ambiguous_resource::<ExtractedUiNodes>()
|
||||
.init_resource::<DrawFunctions<TransparentUi>>()
|
||||
.add_render_command::<TransparentUi, DrawUi>()
|
||||
.configure_sets(
|
||||
ExtractSchedule,
|
||||
(
|
||||
RenderUiSystem::ExtractBackgrounds,
|
||||
RenderUiSystem::ExtractImages,
|
||||
RenderUiSystem::ExtractBorders,
|
||||
RenderUiSystem::ExtractText,
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
(
|
||||
extract_default_ui_camera_view::<Camera2d>,
|
||||
extract_default_ui_camera_view::<Camera3d>,
|
||||
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
|
||||
extract_uinode_borders,
|
||||
extract_uinode_background_colors.in_set(RenderUiSystem::ExtractBackgrounds),
|
||||
extract_uinode_images.in_set(RenderUiSystem::ExtractImages),
|
||||
extract_uinode_borders.in_set(RenderUiSystem::ExtractBorders),
|
||||
extract_uinode_outlines.in_set(RenderUiSystem::ExtractBorders),
|
||||
#[cfg(feature = "bevy_text")]
|
||||
extract_text_uinodes,
|
||||
extract_uinode_outlines,
|
||||
extract_uinode_text.in_set(RenderUiSystem::ExtractText),
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
|
@ -148,6 +162,134 @@ pub struct ExtractedUiNodes {
|
|||
pub uinodes: EntityHashMap<ExtractedUiNode>,
|
||||
}
|
||||
|
||||
pub fn extract_uinode_background_colors(
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
uinode_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
&BackgroundColor,
|
||||
)>,
|
||||
>,
|
||||
) {
|
||||
for (entity, uinode, transform, view_visibility, clip, camera, background_color) in
|
||||
&uinode_query
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip invisible backgrounds
|
||||
if !view_visibility.get() || background_color.0.is_fully_transparent() {
|
||||
continue;
|
||||
}
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: background_color.0.into(),
|
||||
rect: Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
},
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image: AssetId::default(),
|
||||
atlas_size: None,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_uinode_images(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
uinode_query: Extract<
|
||||
Query<(
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
&UiImage,
|
||||
Option<&TextureAtlas>,
|
||||
Option<&ComputedTextureSlices>,
|
||||
)>,
|
||||
>,
|
||||
) {
|
||||
for (uinode, transform, view_visibility, clip, camera, image, atlas, slices) in &uinode_query {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip invisible images
|
||||
if !view_visibility.get() || image.color.is_fully_transparent() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(slices) = slices {
|
||||
extracted_uinodes.uinodes.extend(
|
||||
slices
|
||||
.extract_ui_nodes(transform, uinode, image, clip, camera_entity)
|
||||
.map(|e| (commands.spawn_empty().id(), e)),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let (rect, atlas_size) = match atlas {
|
||||
Some(atlas) => {
|
||||
let Some(layout) = texture_atlases.get(&atlas.layout) else {
|
||||
// Atlas not present in assets resource (should this warn the user?)
|
||||
continue;
|
||||
};
|
||||
let mut atlas_rect = layout.textures[atlas.index].as_rect();
|
||||
let mut atlas_size = layout.size.as_vec2();
|
||||
let scale = uinode.size() / atlas_rect.size();
|
||||
atlas_rect.min *= scale;
|
||||
atlas_rect.max *= scale;
|
||||
atlas_size *= scale;
|
||||
(atlas_rect, Some(atlas_size))
|
||||
}
|
||||
None => (
|
||||
Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
},
|
||||
None,
|
||||
),
|
||||
};
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: image.color.into(),
|
||||
rect,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image: image.texture.id(),
|
||||
atlas_size,
|
||||
flip_x: image.flip_x,
|
||||
flip_y: image.flip_y,
|
||||
camera_entity,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 {
|
||||
match value {
|
||||
Val::Auto => 0.,
|
||||
|
@ -171,12 +313,12 @@ pub fn extract_uinode_borders(
|
|||
(
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&Style,
|
||||
&BorderColor,
|
||||
Option<&Parent>,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
Option<&Parent>,
|
||||
&Style,
|
||||
&BorderColor,
|
||||
),
|
||||
Without<ContentSize>,
|
||||
>,
|
||||
|
@ -185,13 +327,14 @@ pub fn extract_uinode_borders(
|
|||
) {
|
||||
let image = AssetId::<Image>::default();
|
||||
|
||||
for (node, global_transform, style, border_color, parent, view_visibility, clip, camera) in
|
||||
for (node, global_transform, view_visibility, clip, camera, parent, style, border_color) in
|
||||
&uinode_query
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip invisible borders
|
||||
if !view_visibility.get()
|
||||
|| border_color.0.is_fully_transparent()
|
||||
|
@ -290,19 +433,20 @@ pub fn extract_uinode_outlines(
|
|||
Query<(
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&Outline,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
&Outline,
|
||||
)>,
|
||||
>,
|
||||
) {
|
||||
let image = AssetId::<Image>::default();
|
||||
for (node, global_transform, outline, view_visibility, maybe_clip, camera) in &uinode_query {
|
||||
for (node, global_transform, view_visibility, maybe_clip, camera, outline) in &uinode_query {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip invisible outlines
|
||||
if !view_visibility.get()
|
||||
|| outline.color.is_fully_transparent()
|
||||
|
@ -373,104 +517,6 @@ pub fn extract_uinode_outlines(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn extract_uinodes(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
uinode_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&BackgroundColor,
|
||||
Option<&UiImage>,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TextureAtlas>,
|
||||
Option<&TargetCamera>,
|
||||
Option<&ComputedTextureSlices>,
|
||||
)>,
|
||||
>,
|
||||
) {
|
||||
for (
|
||||
entity,
|
||||
uinode,
|
||||
transform,
|
||||
color,
|
||||
maybe_image,
|
||||
view_visibility,
|
||||
clip,
|
||||
atlas,
|
||||
camera,
|
||||
slices,
|
||||
) in uinode_query.iter()
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
// Skip invisible and completely transparent nodes
|
||||
if !view_visibility.get() || color.0.is_fully_transparent() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((image, slices)) = maybe_image.zip(slices) {
|
||||
extracted_uinodes.uinodes.extend(
|
||||
slices
|
||||
.extract_ui_nodes(transform, uinode, color, image, clip, camera_entity)
|
||||
.map(|e| (commands.spawn_empty().id(), e)),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let (image, flip_x, flip_y) = if let Some(image) = maybe_image {
|
||||
(image.texture.id(), image.flip_x, image.flip_y)
|
||||
} else {
|
||||
(AssetId::default(), false, false)
|
||||
};
|
||||
|
||||
let (rect, atlas_size) = match atlas {
|
||||
Some(atlas) => {
|
||||
let Some(layout) = texture_atlases.get(&atlas.layout) else {
|
||||
// Atlas not present in assets resource (should this warn the user?)
|
||||
continue;
|
||||
};
|
||||
let mut atlas_rect = layout.textures[atlas.index].as_rect();
|
||||
let mut atlas_size = layout.size.as_vec2();
|
||||
let scale = uinode.size() / atlas_rect.size();
|
||||
atlas_rect.min *= scale;
|
||||
atlas_rect.max *= scale;
|
||||
atlas_size *= scale;
|
||||
(atlas_rect, Some(atlas_size))
|
||||
}
|
||||
None => (
|
||||
Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
},
|
||||
None,
|
||||
),
|
||||
};
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: color.0.into(),
|
||||
rect,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image,
|
||||
atlas_size,
|
||||
flip_x,
|
||||
flip_y,
|
||||
camera_entity,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The UI camera is "moved back" by this many units (plus the [`UI_CAMERA_TRANSFORM_OFFSET`]) and also has a view
|
||||
/// distance of this many units. This ensures that with a left-handed projection,
|
||||
/// as ui elements are "stacked on top of each other", they are within the camera's view
|
||||
|
@ -546,7 +592,7 @@ pub fn extract_default_ui_camera_view<T: Component>(
|
|||
}
|
||||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
pub fn extract_text_uinodes(
|
||||
pub fn extract_uinode_text(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
||||
|
@ -557,21 +603,22 @@ pub fn extract_text_uinodes(
|
|||
Query<(
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&Text,
|
||||
&TextLayoutInfo,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TargetCamera>,
|
||||
&Text,
|
||||
&TextLayoutInfo,
|
||||
)>,
|
||||
>,
|
||||
) {
|
||||
for (uinode, global_transform, text, text_layout_info, view_visibility, clip, camera) in
|
||||
uinode_query.iter()
|
||||
for (uinode, global_transform, view_visibility, clip, camera, text, text_layout_info) in
|
||||
&uinode_query
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
|
||||
if !view_visibility.get() || uinode.size().x == 0. || uinode.size().y == 0. {
|
||||
continue;
|
||||
|
|
|
@ -74,7 +74,7 @@ where
|
|||
ExtractSchedule,
|
||||
(
|
||||
extract_ui_materials::<M>,
|
||||
extract_ui_material_nodes::<M>.in_set(RenderUiSystem::ExtractNode),
|
||||
extract_ui_material_nodes::<M>.in_set(RenderUiSystem::ExtractBackgrounds),
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
|
|
|
@ -10,7 +10,7 @@ use bevy_sprite::{ImageScaleMode, TextureSlice};
|
|||
use bevy_transform::prelude::*;
|
||||
use bevy_utils::HashSet;
|
||||
|
||||
use crate::{BackgroundColor, CalculatedClip, ExtractedUiNode, Node, UiImage};
|
||||
use crate::{CalculatedClip, ExtractedUiNode, Node, UiImage};
|
||||
|
||||
/// Component storing texture slices for image nodes entities with a tiled or sliced [`ImageScaleMode`]
|
||||
///
|
||||
|
@ -35,7 +35,6 @@ impl ComputedTextureSlices {
|
|||
&'a self,
|
||||
transform: &'a GlobalTransform,
|
||||
node: &'a Node,
|
||||
background_color: &'a BackgroundColor,
|
||||
image: &'a UiImage,
|
||||
clip: Option<&'a CalculatedClip>,
|
||||
camera_entity: Entity,
|
||||
|
@ -60,7 +59,7 @@ impl ComputedTextureSlices {
|
|||
let atlas_size = Some(self.image_size * scale);
|
||||
ExtractedUiNode {
|
||||
stack_index: node.stack_index,
|
||||
color: background_color.0.into(),
|
||||
color: image.color.into(),
|
||||
transform: transform.compute_matrix(),
|
||||
rect,
|
||||
flip_x,
|
||||
|
|
|
@ -1589,7 +1589,6 @@ pub enum GridPlacementError {
|
|||
/// The background color of the node
|
||||
///
|
||||
/// This serves as the "fill" color.
|
||||
/// When combined with [`UiImage`], tints the provided texture.
|
||||
#[derive(Component, Copy, Clone, Debug, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
#[cfg_attr(
|
||||
|
@ -1729,6 +1728,8 @@ impl Outline {
|
|||
#[derive(Component, Clone, Debug, Reflect, Default)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct UiImage {
|
||||
/// The tint color used to draw the image
|
||||
pub color: Color,
|
||||
/// Handle to the texture
|
||||
pub texture: Handle<Image>,
|
||||
/// Whether the image should be flipped along its x-axis
|
||||
|
@ -1745,6 +1746,13 @@ impl UiImage {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the color tint
|
||||
#[must_use]
|
||||
pub const fn with_color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Flip the image along its x-axis
|
||||
#[must_use]
|
||||
pub const fn with_flip_x(mut self) -> Self {
|
||||
|
|
|
@ -147,7 +147,7 @@ fn setup(
|
|||
..default()
|
||||
},
|
||||
border_color: Color::WHITE.into(),
|
||||
background_color: DARK_GRAY.into(),
|
||||
image: UiImage::default().with_color(DARK_GRAY.into()),
|
||||
..default()
|
||||
},
|
||||
))
|
||||
|
|
|
@ -74,7 +74,7 @@ fn setup_menu(mut commands: Commands) {
|
|||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
|
@ -95,21 +95,21 @@ fn setup_menu(mut commands: Commands) {
|
|||
fn menu(
|
||||
mut next_state: ResMut<NextState<AppState>>,
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &mut BackgroundColor),
|
||||
(&Interaction, &mut UiImage),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
) {
|
||||
for (interaction, mut color) in &mut interaction_query {
|
||||
for (interaction, mut image) in &mut interaction_query {
|
||||
match *interaction {
|
||||
Interaction::Pressed => {
|
||||
*color = PRESSED_BUTTON.into();
|
||||
image.color = PRESSED_BUTTON;
|
||||
next_state.set(AppState::InGame);
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
*color = HOVERED_BUTTON.into();
|
||||
image.color = HOVERED_BUTTON;
|
||||
}
|
||||
Interaction::None => {
|
||||
*color = NORMAL_BUTTON.into();
|
||||
image.color = NORMAL_BUTTON;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -345,16 +345,16 @@ mod menu {
|
|||
// This system handles changing all buttons color based on mouse interaction
|
||||
fn button_system(
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &mut BackgroundColor, Option<&SelectedOption>),
|
||||
(&Interaction, &mut UiImage, Option<&SelectedOption>),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
) {
|
||||
for (interaction, mut color, selected) in &mut interaction_query {
|
||||
*color = match (*interaction, selected) {
|
||||
(Interaction::Pressed, _) | (Interaction::None, Some(_)) => PRESSED_BUTTON.into(),
|
||||
(Interaction::Hovered, Some(_)) => HOVERED_PRESSED_BUTTON.into(),
|
||||
(Interaction::Hovered, None) => HOVERED_BUTTON.into(),
|
||||
(Interaction::None, None) => NORMAL_BUTTON.into(),
|
||||
for (interaction, mut image, selected) in &mut interaction_query {
|
||||
image.color = match (*interaction, selected) {
|
||||
(Interaction::Pressed, _) | (Interaction::None, Some(_)) => PRESSED_BUTTON,
|
||||
(Interaction::Hovered, Some(_)) => HOVERED_PRESSED_BUTTON,
|
||||
(Interaction::Hovered, None) => HOVERED_BUTTON,
|
||||
(Interaction::None, None) => NORMAL_BUTTON,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -363,14 +363,14 @@ mod menu {
|
|||
// the button as the one currently selected
|
||||
fn setting_button<T: Resource + Component + PartialEq + Copy>(
|
||||
interaction_query: Query<(&Interaction, &T, Entity), (Changed<Interaction>, With<Button>)>,
|
||||
mut selected_query: Query<(Entity, &mut BackgroundColor), With<SelectedOption>>,
|
||||
mut selected_query: Query<(Entity, &mut UiImage), With<SelectedOption>>,
|
||||
mut commands: Commands,
|
||||
mut setting: ResMut<T>,
|
||||
) {
|
||||
for (interaction, button_setting, entity) in &interaction_query {
|
||||
if *interaction == Interaction::Pressed && *setting != *button_setting {
|
||||
let (previous_button, mut previous_color) = selected_query.single_mut();
|
||||
*previous_color = NORMAL_BUTTON.into();
|
||||
let (previous_button, mut previous_image) = selected_query.single_mut();
|
||||
previous_image.color = NORMAL_BUTTON;
|
||||
commands.entity(previous_button).remove::<SelectedOption>();
|
||||
commands.entity(entity).insert(SelectedOption);
|
||||
*setting = *button_setting;
|
||||
|
@ -456,7 +456,7 @@ mod menu {
|
|||
.spawn((
|
||||
ButtonBundle {
|
||||
style: button_style.clone(),
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
},
|
||||
MenuButtonAction::Play,
|
||||
|
@ -477,7 +477,7 @@ mod menu {
|
|||
.spawn((
|
||||
ButtonBundle {
|
||||
style: button_style.clone(),
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
},
|
||||
MenuButtonAction::Settings,
|
||||
|
@ -498,7 +498,7 @@ mod menu {
|
|||
.spawn((
|
||||
ButtonBundle {
|
||||
style: button_style,
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
},
|
||||
MenuButtonAction::Quit,
|
||||
|
@ -567,7 +567,7 @@ mod menu {
|
|||
.spawn((
|
||||
ButtonBundle {
|
||||
style: button_style.clone(),
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
},
|
||||
action,
|
||||
|
@ -654,7 +654,7 @@ mod menu {
|
|||
height: Val::Px(65.0),
|
||||
..button_style.clone()
|
||||
},
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
},
|
||||
quality_setting,
|
||||
|
@ -675,7 +675,7 @@ mod menu {
|
|||
.spawn((
|
||||
ButtonBundle {
|
||||
style: button_style,
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
},
|
||||
MenuButtonAction::BackToSettings,
|
||||
|
@ -750,7 +750,7 @@ mod menu {
|
|||
height: Val::Px(65.0),
|
||||
..button_style.clone()
|
||||
},
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
},
|
||||
Volume(volume_setting),
|
||||
|
@ -764,7 +764,7 @@ mod menu {
|
|||
.spawn((
|
||||
ButtonBundle {
|
||||
style: button_style,
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
},
|
||||
MenuButtonAction::BackToSettings,
|
||||
|
|
|
@ -97,18 +97,15 @@ fn main() {
|
|||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct IdleColor(BackgroundColor);
|
||||
struct IdleColor(Color);
|
||||
|
||||
fn button_system(
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &mut BackgroundColor, &IdleColor),
|
||||
Changed<Interaction>,
|
||||
>,
|
||||
mut interaction_query: Query<(&Interaction, &mut UiImage, &IdleColor), Changed<Interaction>>,
|
||||
) {
|
||||
for (interaction, mut button_color, IdleColor(idle_color)) in interaction_query.iter_mut() {
|
||||
*button_color = match interaction {
|
||||
for (interaction, mut image, &IdleColor(idle_color)) in interaction_query.iter_mut() {
|
||||
image.color = match interaction {
|
||||
Interaction::Hovered => ORANGE_RED.into(),
|
||||
_ => *idle_color,
|
||||
_ => idle_color,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +145,7 @@ fn setup_flex(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<
|
|||
.spawn(NodeBundle::default())
|
||||
.with_children(|commands| {
|
||||
for row in 0..args.buttons {
|
||||
let color = as_rainbow(row % column.max(1)).into();
|
||||
let color = as_rainbow(row % column.max(1));
|
||||
let border_color = Color::WHITE.with_alpha(0.5).into();
|
||||
spawn_button(
|
||||
commands,
|
||||
|
@ -202,7 +199,7 @@ fn setup_grid(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<
|
|||
.with_children(|commands| {
|
||||
for column in 0..args.buttons {
|
||||
for row in 0..args.buttons {
|
||||
let color = as_rainbow(row % column.max(1)).into();
|
||||
let color = as_rainbow(row % column.max(1));
|
||||
let border_color = Color::WHITE.with_alpha(0.5).into();
|
||||
spawn_button(
|
||||
commands,
|
||||
|
@ -226,7 +223,7 @@ fn setup_grid(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn spawn_button(
|
||||
commands: &mut ChildBuilder,
|
||||
background_color: BackgroundColor,
|
||||
background_color: Color,
|
||||
buttons: f32,
|
||||
column: usize,
|
||||
row: usize,
|
||||
|
@ -249,7 +246,7 @@ fn spawn_button(
|
|||
border,
|
||||
..default()
|
||||
},
|
||||
background_color,
|
||||
image: UiImage::default().with_color(background_color),
|
||||
border_color,
|
||||
..default()
|
||||
},
|
||||
|
|
|
@ -19,33 +19,28 @@ const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
|
|||
|
||||
fn button_system(
|
||||
mut interaction_query: Query<
|
||||
(
|
||||
&Interaction,
|
||||
&mut BackgroundColor,
|
||||
&mut BorderColor,
|
||||
&Children,
|
||||
),
|
||||
(&Interaction, &mut UiImage, &mut BorderColor, &Children),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
for (interaction, mut color, mut border_color, children) in &mut interaction_query {
|
||||
for (interaction, mut image, mut border_color, children) in &mut interaction_query {
|
||||
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||
match *interaction {
|
||||
Interaction::Pressed => {
|
||||
text.sections[0].value = "Press".to_string();
|
||||
*color = PRESSED_BUTTON.into();
|
||||
image.color = PRESSED_BUTTON;
|
||||
border_color.0 = RED.into();
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
text.sections[0].value = "Hover".to_string();
|
||||
*color = HOVERED_BUTTON.into();
|
||||
border_color.0 = WHITE.into();
|
||||
image.color = HOVERED_BUTTON;
|
||||
border_color.0 = Color::WHITE;
|
||||
}
|
||||
Interaction::None => {
|
||||
text.sections[0].value = "Button".to_string();
|
||||
*color = NORMAL_BUTTON.into();
|
||||
border_color.0 = BLACK.into();
|
||||
image.color = NORMAL_BUTTON;
|
||||
border_color.0 = Color::BLACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +74,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
..default()
|
||||
},
|
||||
border_color: BorderColor(Color::BLACK),
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
image: UiImage::default().with_color(NORMAL_BUTTON),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
|
|
|
@ -417,7 +417,7 @@ where
|
|||
padding: UiRect::axes(Val::Px(5.), Val::Px(1.)),
|
||||
..Default::default()
|
||||
},
|
||||
background_color: BackgroundColor(Color::BLACK.with_alpha(0.5)),
|
||||
image: UiImage::default().with_color(Color::BLACK.with_alpha(0.5)),
|
||||
..Default::default()
|
||||
},
|
||||
Target::<T>::new(target),
|
||||
|
@ -461,13 +461,13 @@ fn buttons_handler<T>(
|
|||
}
|
||||
|
||||
fn text_hover(
|
||||
mut button_query: Query<(&Interaction, &mut BackgroundColor, &Children), Changed<Interaction>>,
|
||||
mut button_query: Query<(&Interaction, &mut UiImage, &Children), Changed<Interaction>>,
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
for (interaction, mut background_color, children) in button_query.iter_mut() {
|
||||
for (interaction, mut image, children) in button_query.iter_mut() {
|
||||
match interaction {
|
||||
Interaction::Hovered => {
|
||||
*background_color = BackgroundColor(Color::BLACK.with_alpha(0.6));
|
||||
image.color = Color::BLACK.with_alpha(0.6);
|
||||
for &child in children {
|
||||
if let Ok(mut text) = text_query.get_mut(child) {
|
||||
// Bypass change detection to avoid recomputation of the text when only changing the color
|
||||
|
@ -476,7 +476,7 @@ fn text_hover(
|
|||
}
|
||||
}
|
||||
_ => {
|
||||
*background_color = BackgroundColor(Color::BLACK.with_alpha(0.5));
|
||||
image.color = Color::BLACK.with_alpha(0.5);
|
||||
for &child in children {
|
||||
if let Ok(mut text) = text_query.get_mut(child) {
|
||||
text.bypass_change_detection().sections[0].style.color =
|
||||
|
|
|
@ -95,8 +95,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
min_height: Val::Px(100.),
|
||||
..Default::default()
|
||||
},
|
||||
background_color: Color::WHITE.into(),
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
Interaction::default(),
|
||||
|
|
|
@ -245,12 +245,11 @@ fn spawn_button(
|
|||
margin: UiRect::horizontal(Val::Px(2.)),
|
||||
..Default::default()
|
||||
},
|
||||
background_color: if active {
|
||||
image: UiImage::default().with_color(if active {
|
||||
ACTIVE_BORDER_COLOR
|
||||
} else {
|
||||
INACTIVE_BORDER_COLOR
|
||||
}
|
||||
.into(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
constraint,
|
||||
|
@ -359,6 +358,7 @@ fn update_buttons(
|
|||
fn update_radio_buttons_colors(
|
||||
mut event_reader: EventReader<ButtonActivatedEvent>,
|
||||
button_query: Query<(Entity, &Constraint, &Interaction)>,
|
||||
mut image_query: Query<&mut UiImage>,
|
||||
mut color_query: Query<&mut BackgroundColor>,
|
||||
mut text_query: Query<&mut Text>,
|
||||
children_query: Query<&Children>,
|
||||
|
@ -381,12 +381,10 @@ fn update_radio_buttons_colors(
|
|||
)
|
||||
};
|
||||
|
||||
color_query.get_mut(id).unwrap().0 = border_color;
|
||||
if let Ok(children) = children_query.get(id) {
|
||||
for &child in children {
|
||||
image_query.get_mut(id).unwrap().color = border_color;
|
||||
for &child in children_query.get(id).into_iter().flatten() {
|
||||
color_query.get_mut(child).unwrap().0 = inner_color;
|
||||
if let Ok(grand_children) = children_query.get(child) {
|
||||
for &grandchild in grand_children {
|
||||
for &grandchild in children_query.get(child).into_iter().flatten() {
|
||||
if let Ok(mut text) = text_query.get_mut(grandchild) {
|
||||
text.sections[0].style.color = text_color;
|
||||
}
|
||||
|
@ -396,5 +394,3 @@ fn update_radio_buttons_colors(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
background_color: Color::srgb(0.1, 0.5, 0.1).into(),
|
||||
image: UiImage::default().with_color(Color::srgb(0.1, 0.5, 0.1)),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
|
@ -63,7 +63,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
background_color: Color::srgb(0.5, 0.1, 0.5).into(),
|
||||
image: UiImage::default().with_color(Color::srgb(0.5, 0.1, 0.5)),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! This example illustrates how to use `TextureAtlases` within ui
|
||||
|
||||
use bevy::{color::palettes::basic::YELLOW, prelude::*, winit::WinitSettings};
|
||||
use bevy::{color::palettes::css::*, prelude::*, winit::WinitSettings};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
|
@ -60,6 +60,8 @@ fn setup(
|
|||
..default()
|
||||
},
|
||||
TextureAtlas::from(texture_atlas_handle),
|
||||
BackgroundColor(ANTIQUE_WHITE.into()),
|
||||
Outline::new(Val::Px(8.0), Val::ZERO, CRIMSON.into()),
|
||||
));
|
||||
parent.spawn(TextBundle::from_sections([
|
||||
TextSection::new("press ".to_string(), text_style.clone()),
|
||||
|
|
|
@ -19,25 +19,25 @@ fn main() {
|
|||
|
||||
fn button_system(
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &Children, &mut BackgroundColor),
|
||||
(&Interaction, &Children, &mut UiImage),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
for (interaction, children, mut color) in &mut interaction_query {
|
||||
for (interaction, children, mut image) in &mut interaction_query {
|
||||
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||
match *interaction {
|
||||
Interaction::Pressed => {
|
||||
text.sections[0].value = "Press".to_string();
|
||||
color.0 = GOLD.into();
|
||||
image.color = GOLD.into();
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
text.sections[0].value = "Hover".to_string();
|
||||
color.0 = ORANGE.into();
|
||||
image.color = ORANGE.into();
|
||||
}
|
||||
Interaction::None => {
|
||||
text.sections[0].value = "Button".to_string();
|
||||
color.0 = Color::WHITE;
|
||||
image.color = Color::WHITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,8 +81,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
..default()
|
||||
},
|
||||
image: image.clone().into(),
|
||||
// When combined with an image, this tints the image.
|
||||
background_color: Color::WHITE.into(),
|
||||
..default()
|
||||
},
|
||||
ImageScaleMode::Sliced(slicer.clone()),
|
||||
|
|
Loading…
Reference in a new issue