bevy_ui/
lib.rs

1#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc(
4    html_logo_url = "https://bevy.org/assets/icon.png",
5    html_favicon_url = "https://bevy.org/assets/icon.png"
6)]
7
8//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
9//! # Basic usage
10//! Spawn UI elements with [`widget::Button`], [`ImageNode`](widget::ImageNode), [`Text`](prelude::Text) and [`Node`]
11//! This UI is laid out with the Flexbox and CSS Grid layout models (see <https://cssreference.io/flexbox/>)
12
13pub mod interaction_states;
14pub mod measurement;
15pub mod update;
16pub mod widget;
17
18pub mod gradients;
19#[cfg(feature = "bevy_ui_picking_backend")]
20pub mod picking_backend;
21pub mod ui_transform;
22
23use bevy_derive::{Deref, DerefMut};
24#[cfg(feature = "bevy_ui_picking_backend")]
25use bevy_picking::PickingSystems;
26use bevy_reflect::{std_traits::ReflectDefault, Reflect};
27mod accessibility;
28// This module is not re-exported, but is instead made public.
29// This is intended to discourage accidental use of the experimental API.
30pub mod experimental;
31mod focus;
32mod geometry;
33mod layout;
34mod stack;
35mod ui_node;
36
37pub use focus::*;
38pub use geometry::*;
39pub use gradients::*;
40pub use interaction_states::{Checkable, Checked, InteractionDisabled, Pressed};
41pub use layout::*;
42pub use measurement::*;
43pub use ui_node::*;
44pub use ui_transform::*;
45
46/// The UI prelude.
47///
48/// This includes the most common types in this crate, re-exported for your convenience.
49pub mod prelude {
50    #[doc(hidden)]
51    #[cfg(feature = "bevy_ui_picking_backend")]
52    pub use crate::picking_backend::{UiPickingCamera, UiPickingPlugin, UiPickingSettings};
53    #[doc(hidden)]
54    pub use crate::widget::{Text, TextShadow, TextUiReader, TextUiWriter};
55    #[doc(hidden)]
56    pub use {
57        crate::{
58            geometry::*,
59            gradients::*,
60            ui_node::*,
61            ui_transform::*,
62            widget::{Button, ImageNode, Label, NodeImageMode, ViewportNode},
63            Interaction, UiScale,
64        },
65        // `bevy_sprite` re-exports for texture slicing
66        bevy_sprite::{BorderRect, SliceScaleMode, SpriteImageMode, TextureSlicer},
67        bevy_text::TextBackgroundColor,
68    };
69}
70
71use bevy_app::{prelude::*, AnimationSystems, HierarchyPropagatePlugin, PropagateSet};
72use bevy_camera::CameraUpdateSystems;
73use bevy_ecs::prelude::*;
74use bevy_input::InputSystems;
75use bevy_transform::TransformSystems;
76use layout::ui_surface::UiSurface;
77use stack::ui_stack_system;
78pub use stack::UiStack;
79use update::{propagate_ui_target_cameras, update_clipping_system};
80
81/// The basic plugin for Bevy UI
82#[derive(Default)]
83pub struct UiPlugin;
84
85/// The label enum labeling the types of systems in the Bevy UI
86#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
87pub enum UiSystems {
88    /// After this label, input interactions with UI entities have been updated for this frame.
89    ///
90    /// Runs in [`PreUpdate`].
91    Focus,
92    /// All UI systems in [`PostUpdate`] will run in or after this label.
93    Prepare,
94    /// Propagate UI component values needed by layout.
95    Propagate,
96    /// Update content requirements before layout.
97    Content,
98    /// After this label, the ui layout state has been updated.
99    ///
100    /// Runs in [`PostUpdate`].
101    Layout,
102    /// UI systems ordered after [`UiSystems::Layout`].
103    ///
104    /// Runs in [`PostUpdate`].
105    PostLayout,
106    /// After this label, the [`UiStack`] resource has been updated.
107    ///
108    /// Runs in [`PostUpdate`].
109    Stack,
110}
111
112/// Deprecated alias for [`UiSystems`].
113#[deprecated(since = "0.17.0", note = "Renamed to `UiSystems`.")]
114pub type UiSystem = UiSystems;
115
116/// The current scale of the UI.
117///
118/// A multiplier to fixed-sized ui values.
119/// **Note:** This will only affect fixed ui values like [`Val::Px`]
120#[derive(Debug, Reflect, Resource, Deref, DerefMut)]
121#[reflect(Resource, Debug, Default)]
122pub struct UiScale(pub f32);
123
124impl Default for UiScale {
125    fn default() -> Self {
126        Self(1.0)
127    }
128}
129
130// Marks systems that can be ambiguous with [`widget::text_system`] if the `bevy_text` feature is enabled.
131// See https://github.com/bevyengine/bevy/pull/11391 for more details.
132#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
133struct AmbiguousWithText;
134
135#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
136struct AmbiguousWithUpdateText2dLayout;
137
138impl Plugin for UiPlugin {
139    fn build(&self, app: &mut App) {
140        app.init_resource::<UiSurface>()
141            .init_resource::<UiScale>()
142            .init_resource::<UiStack>()
143            .configure_sets(
144                PostUpdate,
145                (
146                    CameraUpdateSystems,
147                    UiSystems::Prepare.after(AnimationSystems),
148                    UiSystems::Propagate,
149                    UiSystems::Content,
150                    UiSystems::Layout,
151                    UiSystems::PostLayout,
152                )
153                    .chain(),
154            )
155            .configure_sets(
156                PostUpdate,
157                PropagateSet::<ComputedUiTargetCamera>::default().in_set(UiSystems::Propagate),
158            )
159            .add_plugins(HierarchyPropagatePlugin::<ComputedUiTargetCamera>::new(
160                PostUpdate,
161            ))
162            .configure_sets(
163                PostUpdate,
164                PropagateSet::<ComputedUiRenderTargetInfo>::default().in_set(UiSystems::Propagate),
165            )
166            .add_plugins(HierarchyPropagatePlugin::<ComputedUiRenderTargetInfo>::new(
167                PostUpdate,
168            ))
169            .add_systems(
170                PreUpdate,
171                ui_focus_system.in_set(UiSystems::Focus).after(InputSystems),
172            );
173
174        #[cfg(feature = "bevy_ui_picking_backend")]
175        app.add_plugins(picking_backend::UiPickingPlugin)
176            .add_systems(
177                First,
178                widget::viewport_picking.in_set(PickingSystems::PostInput),
179            );
180
181        let ui_layout_system_config = ui_layout_system
182            .in_set(UiSystems::Layout)
183            .before(TransformSystems::Propagate);
184
185        let ui_layout_system_config = ui_layout_system_config
186            // Text and Text2D operate on disjoint sets of entities
187            .ambiguous_with(bevy_sprite::update_text2d_layout)
188            .ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_sprite::Text2d>);
189
190        app.add_systems(
191            PostUpdate,
192            (
193                propagate_ui_target_cameras.in_set(UiSystems::Prepare),
194                ui_layout_system_config,
195                ui_stack_system
196                    .in_set(UiSystems::Stack)
197                    // These systems don't care about stack index
198                    .ambiguous_with(widget::measure_text_system)
199                    .ambiguous_with(update_clipping_system)
200                    .ambiguous_with(ui_layout_system)
201                    .ambiguous_with(widget::update_viewport_render_target_size)
202                    .in_set(AmbiguousWithText),
203                update_clipping_system.after(TransformSystems::Propagate),
204                // Potential conflicts: `Assets<Image>`
205                // They run independently since `widget::image_node_system` will only ever observe
206                // its own ImageNode, and `widget::text_system` & `bevy_text::update_text2d_layout`
207                // will never modify a pre-existing `Image` asset.
208                widget::update_image_content_size_system
209                    .in_set(UiSystems::Content)
210                    .in_set(AmbiguousWithText)
211                    .in_set(AmbiguousWithUpdateText2dLayout),
212                // Potential conflicts: `Assets<Image>`
213                // `widget::text_system` and `bevy_text::update_text2d_layout` run independently
214                // since this system will only ever update viewport images.
215                widget::update_viewport_render_target_size
216                    .in_set(UiSystems::PostLayout)
217                    .in_set(AmbiguousWithText)
218                    .in_set(AmbiguousWithUpdateText2dLayout),
219            ),
220        );
221
222        build_text_interop(app);
223    }
224}
225
226fn build_text_interop(app: &mut App) {
227    use widget::Text;
228
229    app.add_systems(
230        PostUpdate,
231        (
232            (
233                bevy_text::detect_text_needs_rerender::<Text>,
234                widget::measure_text_system,
235            )
236                .chain()
237                .in_set(UiSystems::Content)
238                // Text and Text2d are independent.
239                .ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_sprite::Text2d>)
240                // Potential conflict: `Assets<Image>`
241                // Since both systems will only ever insert new [`Image`] assets,
242                // they will never observe each other's effects.
243                .ambiguous_with(bevy_sprite::update_text2d_layout)
244                // We assume Text is on disjoint UI entities to ImageNode and UiTextureAtlasImage
245                // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
246                .ambiguous_with(widget::update_image_content_size_system),
247            widget::text_system
248                .in_set(UiSystems::PostLayout)
249                .after(bevy_text::remove_dropped_font_atlas_sets)
250                .before(bevy_asset::AssetEventSystems)
251                // Text2d and bevy_ui text are entirely on separate entities
252                .ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_sprite::Text2d>)
253                .ambiguous_with(bevy_sprite::update_text2d_layout)
254                .ambiguous_with(bevy_sprite::calculate_bounds_text2d),
255        ),
256    );
257
258    app.add_plugins(accessibility::AccessibilityPlugin);
259
260    app.add_observer(interaction_states::on_add_disabled)
261        .add_observer(interaction_states::on_remove_disabled)
262        .add_observer(interaction_states::on_add_checkable)
263        .add_observer(interaction_states::on_remove_checkable)
264        .add_observer(interaction_states::on_add_checked)
265        .add_observer(interaction_states::on_remove_checked);
266
267    app.configure_sets(
268        PostUpdate,
269        AmbiguousWithText.ambiguous_with(widget::text_system),
270    );
271
272    app.configure_sets(
273        PostUpdate,
274        AmbiguousWithUpdateText2dLayout.ambiguous_with(bevy_sprite::update_text2d_layout),
275    );
276}