mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.** # Objective - Implement https://github.com/bevyengine/bevy/discussions/15014 ## Solution This implements [cart's proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459) faithfully except for one change. I separated `TextSpan` from `TextSpan2d` because `TextSpan` needs to require the `GhostNode` component, which is a `bevy_ui` component only usable by UI. Extra changes: - Added `EntityCommands::commands_mut` that returns a mutable reference. This is a blocker for extension methods that return something other than `self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable reference for this reason. ## Testing - [x] Text examples all work. --- ## Showcase TODO: showcase-worthy ## Migration Guide TODO: very breaking ### Accessing text spans by index Text sections are now text sections on different entities in a hierarchy, Use the new `TextReader` and `TextWriter` system parameters to access spans by index. Before: ```rust fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) { let text = query.single_mut(); text.sections[1].value = format_time(time.elapsed()); } ``` After: ```rust fn refresh_text( query: Query<Entity, With<TimeText>>, mut writer: UiTextWriter, time: Res<Time> ) { let entity = query.single(); *writer.text(entity, 1) = format_time(time.elapsed()); } ``` ### Iterating text spans Text spans are now entities in a hierarchy, so the new `UiTextReader` and `UiTextWriter` system parameters provide ways to iterate that hierarchy. The `UiTextReader::iter` method will give you a normal iterator over spans, and `UiTextWriter::for_each` lets you visit each of the spans. --------- Co-authored-by: ickshonpe <david.curthoys@googlemail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
0b2e0cfaca
commit
c2c19e5ae4
146 changed files with 3102 additions and 2712 deletions
|
@ -7,15 +7,17 @@ use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChangesMut,
|
change_detection::DetectChangesMut,
|
||||||
component::Component,
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
query::With,
|
query::With,
|
||||||
schedule::{common_conditions::resource_changed, IntoSystemConfigs},
|
schedule::{common_conditions::resource_changed, IntoSystemConfigs},
|
||||||
system::{Commands, Query, Res, Resource},
|
system::{Commands, Query, Res, Resource},
|
||||||
};
|
};
|
||||||
use bevy_hierarchy::{BuildChildren, ChildBuild};
|
use bevy_hierarchy::{BuildChildren, ChildBuild};
|
||||||
use bevy_render::view::Visibility;
|
use bevy_render::view::Visibility;
|
||||||
use bevy_text::{Font, Text, TextSection, TextStyle};
|
use bevy_text::{Font, TextSpan, TextStyle};
|
||||||
use bevy_ui::{
|
use bevy_ui::{
|
||||||
node_bundles::{NodeBundle, TextBundle},
|
node_bundles::NodeBundle,
|
||||||
|
widget::{Text, UiTextWriter},
|
||||||
GlobalZIndex, PositionType, Style,
|
GlobalZIndex, PositionType, Style,
|
||||||
};
|
};
|
||||||
use bevy_utils::default;
|
use bevy_utils::default;
|
||||||
|
@ -72,6 +74,7 @@ impl Default for FpsOverlayConfig {
|
||||||
font: Handle::<Font>::default(),
|
font: Handle::<Font>::default(),
|
||||||
font_size: 32.0,
|
font_size: 32.0,
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
|
..default()
|
||||||
},
|
},
|
||||||
enabled: true,
|
enabled: true,
|
||||||
}
|
}
|
||||||
|
@ -95,22 +98,25 @@ fn setup(mut commands: Commands, overlay_config: Res<FpsOverlayConfig>) {
|
||||||
},
|
},
|
||||||
GlobalZIndex(FPS_OVERLAY_ZINDEX),
|
GlobalZIndex(FPS_OVERLAY_ZINDEX),
|
||||||
))
|
))
|
||||||
.with_children(|c| {
|
.with_children(|p| {
|
||||||
c.spawn((
|
p.spawn((
|
||||||
TextBundle::from_sections([
|
Text::new("FPS: "),
|
||||||
TextSection::new("FPS: ", overlay_config.text_config.clone()),
|
overlay_config.text_config.clone(),
|
||||||
TextSection::from_style(overlay_config.text_config.clone()),
|
|
||||||
]),
|
|
||||||
FpsText,
|
FpsText,
|
||||||
));
|
))
|
||||||
|
.with_child((TextSpan::default(), overlay_config.text_config.clone()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_text(diagnostic: Res<DiagnosticsStore>, mut query: Query<&mut Text, With<FpsText>>) {
|
fn update_text(
|
||||||
for mut text in &mut query {
|
diagnostic: Res<DiagnosticsStore>,
|
||||||
|
query: Query<Entity, With<FpsText>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
|
) {
|
||||||
|
for entity in &query {
|
||||||
if let Some(fps) = diagnostic.get(&FrameTimeDiagnosticsPlugin::FPS) {
|
if let Some(fps) = diagnostic.get(&FrameTimeDiagnosticsPlugin::FPS) {
|
||||||
if let Some(value) = fps.smoothed() {
|
if let Some(value) = fps.smoothed() {
|
||||||
text.sections[1].value = format!("{value:.2}");
|
*writer.text(entity, 1) = format!("{value:.2}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,12 +124,13 @@ fn update_text(diagnostic: Res<DiagnosticsStore>, mut query: Query<&mut Text, Wi
|
||||||
|
|
||||||
fn customize_text(
|
fn customize_text(
|
||||||
overlay_config: Res<FpsOverlayConfig>,
|
overlay_config: Res<FpsOverlayConfig>,
|
||||||
mut query: Query<&mut Text, With<FpsText>>,
|
query: Query<Entity, With<FpsText>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
for mut text in &mut query {
|
for entity in &query {
|
||||||
for section in text.sections.iter_mut() {
|
writer.for_each_style(entity, |mut style| {
|
||||||
section.style = overlay_config.text_config.clone();
|
*style = overlay_config.text_config.clone();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -939,7 +939,7 @@ pub struct EntityCommands<'a> {
|
||||||
pub(crate) commands: Commands<'a, 'a>,
|
pub(crate) commands: Commands<'a, 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityCommands<'_> {
|
impl<'a> EntityCommands<'a> {
|
||||||
/// Returns the [`Entity`] id of the entity.
|
/// Returns the [`Entity`] id of the entity.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -1533,6 +1533,11 @@ impl EntityCommands<'_> {
|
||||||
self.commands.reborrow()
|
self.commands.reborrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the underlying [`Commands`].
|
||||||
|
pub fn commands_mut(&mut self) -> &mut Commands<'a, 'a> {
|
||||||
|
&mut self.commands
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends a [`Trigger`] targeting this entity. This will run any [`Observer`] of the `event` that
|
/// Sends a [`Trigger`] targeting this entity. This will run any [`Observer`] of the `event` that
|
||||||
/// watches this entity.
|
/// watches this entity.
|
||||||
///
|
///
|
||||||
|
|
|
@ -18,6 +18,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.15.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.15.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
|
||||||
|
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.15.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.15.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
|
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
|
||||||
"bevy",
|
"bevy",
|
||||||
|
@ -36,6 +37,7 @@ derive_more = { version = "1", default-features = false, features = [
|
||||||
"display",
|
"display",
|
||||||
] }
|
] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
smallvec = "1.13"
|
||||||
unicode-bidi = "0.3.13"
|
unicode-bidi = "0.3.13"
|
||||||
sys-locale = "0.3.0"
|
sys-locale = "0.3.0"
|
||||||
|
|
||||||
|
|
|
@ -60,9 +60,9 @@ pub struct FontAtlasKey(pub u32, pub FontSmoothing);
|
||||||
/// A `FontAtlasSet` is an [`Asset`].
|
/// A `FontAtlasSet` is an [`Asset`].
|
||||||
///
|
///
|
||||||
/// There is one `FontAtlasSet` for each font:
|
/// There is one `FontAtlasSet` for each font:
|
||||||
/// - When a [`Font`] is loaded as an asset and then used in [`Text`](crate::Text),
|
/// - When a [`Font`] is loaded as an asset and then used in [`TextStyle`](crate::TextStyle),
|
||||||
/// a `FontAtlasSet` asset is created from a weak handle to the `Font`.
|
/// a `FontAtlasSet` asset is created from a weak handle to the `Font`.
|
||||||
/// - ~When a font is loaded as a system font, and then used in [`Text`](crate::Text),
|
/// - ~When a font is loaded as a system font, and then used in [`TextStyle`](crate::TextStyle),
|
||||||
/// a `FontAtlasSet` asset is created and stored with a strong handle to the `FontAtlasSet`.~
|
/// a `FontAtlasSet` asset is created and stored with a strong handle to the `FontAtlasSet`.~
|
||||||
/// (*Note that system fonts are not currently supported by the `TextPipeline`.*)
|
/// (*Note that system fonts are not currently supported by the `TextPipeline`.*)
|
||||||
///
|
///
|
||||||
|
|
|
@ -13,14 +13,14 @@ use bevy_sprite::TextureAtlasLayout;
|
||||||
/// Used in [`TextPipeline::queue_text`](crate::TextPipeline::queue_text) and [`crate::TextLayoutInfo`] for rendering glyphs.
|
/// Used in [`TextPipeline::queue_text`](crate::TextPipeline::queue_text) and [`crate::TextLayoutInfo`] for rendering glyphs.
|
||||||
#[derive(Debug, Clone, Reflect)]
|
#[derive(Debug, Clone, Reflect)]
|
||||||
pub struct PositionedGlyph {
|
pub struct PositionedGlyph {
|
||||||
/// The position of the glyph in the [`Text`](crate::Text)'s bounding box.
|
/// The position of the glyph in the text block's bounding box.
|
||||||
pub position: Vec2,
|
pub position: Vec2,
|
||||||
/// The width and height of the glyph in logical pixels.
|
/// The width and height of the glyph in logical pixels.
|
||||||
pub size: Vec2,
|
pub size: Vec2,
|
||||||
/// Information about the glyph's atlas.
|
/// Information about the glyph's atlas.
|
||||||
pub atlas_info: GlyphAtlasInfo,
|
pub atlas_info: GlyphAtlasInfo,
|
||||||
/// The index of the glyph in the [`Text`](crate::Text)'s sections.
|
/// The index of the glyph in the [`ComputedTextBlock`](crate::ComputedTextBlock)'s tracked spans.
|
||||||
pub section_index: usize,
|
pub span_index: usize,
|
||||||
/// TODO: In order to do text editing, we need access to the size of glyphs and their index in the associated String.
|
/// TODO: In order to do text editing, we need access to the size of glyphs and their index in the associated String.
|
||||||
/// For example, to figure out where to place the cursor in an input box from the mouse's position.
|
/// For example, to figure out where to place the cursor in an input box from the mouse's position.
|
||||||
/// Without this, it's only possible in texts where each glyph is one byte. Cosmic text has methods for this
|
/// Without this, it's only possible in texts where each glyph is one byte. Cosmic text has methods for this
|
||||||
|
@ -30,17 +30,12 @@ pub struct PositionedGlyph {
|
||||||
|
|
||||||
impl PositionedGlyph {
|
impl PositionedGlyph {
|
||||||
/// Creates a new [`PositionedGlyph`]
|
/// Creates a new [`PositionedGlyph`]
|
||||||
pub fn new(
|
pub fn new(position: Vec2, size: Vec2, atlas_info: GlyphAtlasInfo, span_index: usize) -> Self {
|
||||||
position: Vec2,
|
|
||||||
size: Vec2,
|
|
||||||
atlas_info: GlyphAtlasInfo,
|
|
||||||
section_index: usize,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
size,
|
size,
|
||||||
atlas_info,
|
atlas_info,
|
||||||
section_index,
|
span_index,
|
||||||
byte_index: 0,
|
byte_index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
//!
|
//!
|
||||||
//! The [`TextPipeline`] resource does all of the heavy lifting for rendering text.
|
//! The [`TextPipeline`] resource does all of the heavy lifting for rendering text.
|
||||||
//!
|
//!
|
||||||
//! [`Text`] is first measured by creating a [`TextMeasureInfo`] in [`TextPipeline::create_text_measure`],
|
//! UI `Text` is first measured by creating a [`TextMeasureInfo`] in [`TextPipeline::create_text_measure`],
|
||||||
//! which is called by the `measure_text_system` system of `bevy_ui`.
|
//! which is called by the `measure_text_system` system of `bevy_ui`.
|
||||||
//!
|
//!
|
||||||
//! Note that text measurement is only relevant in a UI context.
|
//! Note that text measurement is only relevant in a UI context.
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
//! or [`text2d::update_text2d_layout`] system (in a 2d world space context)
|
//! or [`text2d::update_text2d_layout`] system (in a 2d world space context)
|
||||||
//! passes it into [`TextPipeline::queue_text`], which:
|
//! passes it into [`TextPipeline::queue_text`], which:
|
||||||
//!
|
//!
|
||||||
//! 1. creates a [`Buffer`](cosmic_text::Buffer) from the [`TextSection`]s, generating new [`FontAtlasSet`]s if necessary.
|
//! 1. updates a [`Buffer`](cosmic_text::Buffer) from the [`TextSpan`]s, generating new [`FontAtlasSet`]s if necessary.
|
||||||
//! 2. iterates over each glyph in the [`Buffer`](cosmic_text::Buffer) to create a [`PositionedGlyph`],
|
//! 2. iterates over each glyph in the [`Buffer`](cosmic_text::Buffer) to create a [`PositionedGlyph`],
|
||||||
//! retrieving glyphs from the cache, or rasterizing to a [`FontAtlas`] if necessary.
|
//! retrieving glyphs from the cache, or rasterizing to a [`FontAtlas`] if necessary.
|
||||||
//! 3. [`PositionedGlyph`]s are stored in a [`TextLayoutInfo`],
|
//! 3. [`PositionedGlyph`]s are stored in a [`TextLayoutInfo`],
|
||||||
|
@ -43,6 +43,7 @@ mod glyph;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod text;
|
mod text;
|
||||||
mod text2d;
|
mod text2d;
|
||||||
|
mod text_access;
|
||||||
|
|
||||||
pub use cosmic_text;
|
pub use cosmic_text;
|
||||||
|
|
||||||
|
@ -56,13 +57,17 @@ pub use glyph::*;
|
||||||
pub use pipeline::*;
|
pub use pipeline::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
pub use text2d::*;
|
pub use text2d::*;
|
||||||
|
pub use text_access::*;
|
||||||
|
|
||||||
/// The text prelude.
|
/// The text prelude.
|
||||||
///
|
///
|
||||||
/// This includes the most common types in this crate, re-exported for your convenience.
|
/// This includes the most common types in this crate, re-exported for your convenience.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{Font, JustifyText, Text, Text2dBundle, TextError, TextSection, TextStyle};
|
pub use crate::{
|
||||||
|
Font, JustifyText, LineBreak, Text2d, TextBlock, TextError, TextReader2d, TextSpan,
|
||||||
|
TextStyle, TextWriter2d,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
|
@ -87,7 +92,7 @@ pub const DEFAULT_FONT_DATA: &[u8] = include_bytes!("FiraMono-subset.ttf");
|
||||||
pub struct TextPlugin;
|
pub struct TextPlugin;
|
||||||
|
|
||||||
/// Text is rendered for two different view projections;
|
/// Text is rendered for two different view projections;
|
||||||
/// 2-dimensional text ([`Text2dBundle`]) is rendered in "world space" with a `BottomToTop` Y-axis,
|
/// 2-dimensional text ([`Text2d`]) is rendered in "world space" with a `BottomToTop` Y-axis,
|
||||||
/// while UI is rendered with a `TopToBottom` Y-axis.
|
/// while UI is rendered with a `TopToBottom` Y-axis.
|
||||||
/// This matters for text because the glyph positioning is different in either layout.
|
/// This matters for text because the glyph positioning is different in either layout.
|
||||||
/// For `TopToBottom`, 0 is the top of the text, while for `BottomToTop` 0 is the bottom.
|
/// For `TopToBottom`, 0 is the top of the text, while for `BottomToTop` 0 is the bottom.
|
||||||
|
@ -98,35 +103,37 @@ pub enum YAxisOrientation {
|
||||||
BottomToTop,
|
BottomToTop,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenient alias for `With<Text>`, for use with
|
/// System set in [`PostUpdate`] where all 2d text update systems are executed.
|
||||||
/// [`bevy_render::view::VisibleEntities`].
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
pub type WithText = With<Text>;
|
pub struct Update2dText;
|
||||||
|
|
||||||
impl Plugin for TextPlugin {
|
impl Plugin for TextPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_asset::<Font>()
|
app.init_asset::<Font>()
|
||||||
.register_type::<Text>()
|
.register_type::<Text2d>()
|
||||||
|
.register_type::<TextSpan>()
|
||||||
.register_type::<TextBounds>()
|
.register_type::<TextBounds>()
|
||||||
.init_asset_loader::<FontLoader>()
|
.init_asset_loader::<FontLoader>()
|
||||||
.init_resource::<FontAtlasSets>()
|
.init_resource::<FontAtlasSets>()
|
||||||
.init_resource::<TextPipeline>()
|
.init_resource::<TextPipeline>()
|
||||||
.init_resource::<CosmicFontSystem>()
|
.init_resource::<CosmicFontSystem>()
|
||||||
.init_resource::<SwashCache>()
|
.init_resource::<SwashCache>()
|
||||||
|
.init_resource::<TextIterScratch>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
calculate_bounds_text2d
|
remove_dropped_font_atlas_sets,
|
||||||
.in_set(VisibilitySystems::CalculateBounds)
|
detect_text_needs_rerender::<Text2d>,
|
||||||
.after(update_text2d_layout),
|
|
||||||
update_text2d_layout
|
update_text2d_layout
|
||||||
.after(remove_dropped_font_atlas_sets)
|
|
||||||
// Potential conflict: `Assets<Image>`
|
// Potential conflict: `Assets<Image>`
|
||||||
// In practice, they run independently since `bevy_render::camera_update_system`
|
// In practice, they run independently since `bevy_render::camera_update_system`
|
||||||
// will only ever observe its own render target, and `update_text2d_layout`
|
// will only ever observe its own render target, and `update_text2d_layout`
|
||||||
// will never modify a pre-existing `Image` asset.
|
// will never modify a pre-existing `Image` asset.
|
||||||
.ambiguous_with(CameraUpdateSystem),
|
.ambiguous_with(CameraUpdateSystem),
|
||||||
remove_dropped_font_atlas_sets,
|
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
|
||||||
),
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(Update2dText),
|
||||||
)
|
)
|
||||||
.add_systems(Last, trim_cosmic_cache);
|
.add_systems(Last, trim_cosmic_cache);
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ use bevy_utils::HashMap;
|
||||||
use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap};
|
use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::TextError, CosmicBuffer, Font, FontAtlasSets, FontSmoothing, JustifyText, LineBreak,
|
error::TextError, ComputedTextBlock, Font, FontAtlasSets, FontSmoothing, JustifyText,
|
||||||
PositionedGlyph, TextBounds, TextSection, TextStyle, YAxisOrientation,
|
LineBreak, PositionedGlyph, TextBlock, TextBounds, TextEntity, TextStyle, YAxisOrientation,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A wrapper resource around a [`cosmic_text::FontSystem`]
|
/// A wrapper resource around a [`cosmic_text::FontSystem`]
|
||||||
|
@ -60,7 +60,7 @@ struct FontFaceInfo {
|
||||||
family_name: Arc<str>,
|
family_name: Arc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `TextPipeline` is used to layout and render [`Text`](crate::Text).
|
/// The `TextPipeline` is used to layout and render text blocks (see `Text`/[`Text2d`](crate::Text2d)).
|
||||||
///
|
///
|
||||||
/// See the [crate-level documentation](crate) for more information.
|
/// See the [crate-level documentation](crate) for more information.
|
||||||
#[derive(Default, Resource)]
|
#[derive(Default, Resource)]
|
||||||
|
@ -71,6 +71,8 @@ pub struct TextPipeline {
|
||||||
///
|
///
|
||||||
/// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10).
|
/// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10).
|
||||||
spans_buffer: Vec<(usize, &'static str, &'static TextStyle, FontFaceInfo)>,
|
spans_buffer: Vec<(usize, &'static str, &'static TextStyle, FontFaceInfo)>,
|
||||||
|
/// Buffered vec for collecting info for glyph assembly.
|
||||||
|
glyph_info: Vec<(AssetId<Font>, FontSmoothing)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextPipeline {
|
impl TextPipeline {
|
||||||
|
@ -81,12 +83,12 @@ impl TextPipeline {
|
||||||
pub fn update_buffer<'a>(
|
pub fn update_buffer<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
fonts: &Assets<Font>,
|
fonts: &Assets<Font>,
|
||||||
text_spans: impl Iterator<Item = (&'a str, &'a TextStyle)>,
|
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>,
|
||||||
linebreak: LineBreak,
|
linebreak: LineBreak,
|
||||||
|
justify: JustifyText,
|
||||||
bounds: TextBounds,
|
bounds: TextBounds,
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
buffer: &mut CosmicBuffer,
|
computed: &mut ComputedTextBlock,
|
||||||
alignment: JustifyText,
|
|
||||||
font_system: &mut CosmicFontSystem,
|
font_system: &mut CosmicFontSystem,
|
||||||
) -> Result<(), TextError> {
|
) -> Result<(), TextError> {
|
||||||
let font_system = &mut font_system.0;
|
let font_system = &mut font_system.0;
|
||||||
|
@ -100,7 +102,9 @@ impl TextPipeline {
|
||||||
.map(|_| -> (usize, &str, &TextStyle, FontFaceInfo) { unreachable!() })
|
.map(|_| -> (usize, &str, &TextStyle, FontFaceInfo) { unreachable!() })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (span_index, (span, style)) in text_spans.enumerate() {
|
computed.entities.clear();
|
||||||
|
|
||||||
|
for (span_index, (entity, depth, span, style)) in text_spans.enumerate() {
|
||||||
// Return early if a font is not loaded yet.
|
// Return early if a font is not loaded yet.
|
||||||
if !fonts.contains(style.font.id()) {
|
if !fonts.contains(style.font.id()) {
|
||||||
spans.clear();
|
spans.clear();
|
||||||
|
@ -116,6 +120,9 @@ impl TextPipeline {
|
||||||
return Err(TextError::NoSuchFont);
|
return Err(TextError::NoSuchFont);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save this span entity in the computed text block.
|
||||||
|
computed.entities.push(TextEntity { entity, depth });
|
||||||
|
|
||||||
// Get max font size for use in cosmic Metrics.
|
// Get max font size for use in cosmic Metrics.
|
||||||
font_size = font_size.max(style.font_size);
|
font_size = font_size.max(style.font_size);
|
||||||
|
|
||||||
|
@ -152,6 +159,7 @@ impl TextPipeline {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the buffer.
|
// Update the buffer.
|
||||||
|
let buffer = &mut computed.buffer;
|
||||||
buffer.set_metrics(font_system, metrics);
|
buffer.set_metrics(font_system, metrics);
|
||||||
buffer.set_size(font_system, bounds.width, bounds.height);
|
buffer.set_size(font_system, bounds.width, bounds.height);
|
||||||
|
|
||||||
|
@ -170,7 +178,7 @@ impl TextPipeline {
|
||||||
// PERF: https://github.com/pop-os/cosmic-text/issues/166:
|
// PERF: https://github.com/pop-os/cosmic-text/issues/166:
|
||||||
// Setting alignment afterwards appears to invalidate some layouting performed by `set_text` which is presumably not free?
|
// Setting alignment afterwards appears to invalidate some layouting performed by `set_text` which is presumably not free?
|
||||||
for buffer_line in buffer.lines.iter_mut() {
|
for buffer_line in buffer.lines.iter_mut() {
|
||||||
buffer_line.set_align(Some(alignment.into()));
|
buffer_line.set_align(Some(justify.into()));
|
||||||
}
|
}
|
||||||
buffer.shape_until_scroll(font_system, false);
|
buffer.shape_until_scroll(font_system, false);
|
||||||
|
|
||||||
|
@ -189,47 +197,54 @@ impl TextPipeline {
|
||||||
/// Produces a [`TextLayoutInfo`], containing [`PositionedGlyph`]s
|
/// Produces a [`TextLayoutInfo`], containing [`PositionedGlyph`]s
|
||||||
/// which contain information for rendering the text.
|
/// which contain information for rendering the text.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn queue_text(
|
pub fn queue_text<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout_info: &mut TextLayoutInfo,
|
layout_info: &mut TextLayoutInfo,
|
||||||
fonts: &Assets<Font>,
|
fonts: &Assets<Font>,
|
||||||
sections: &[TextSection],
|
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>,
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
text_alignment: JustifyText,
|
block: &TextBlock,
|
||||||
linebreak: LineBreak,
|
|
||||||
font_smoothing: FontSmoothing,
|
|
||||||
bounds: TextBounds,
|
bounds: TextBounds,
|
||||||
font_atlas_sets: &mut FontAtlasSets,
|
font_atlas_sets: &mut FontAtlasSets,
|
||||||
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
y_axis_orientation: YAxisOrientation,
|
y_axis_orientation: YAxisOrientation,
|
||||||
buffer: &mut CosmicBuffer,
|
computed: &mut ComputedTextBlock,
|
||||||
font_system: &mut CosmicFontSystem,
|
font_system: &mut CosmicFontSystem,
|
||||||
swash_cache: &mut SwashCache,
|
swash_cache: &mut SwashCache,
|
||||||
) -> Result<(), TextError> {
|
) -> Result<(), TextError> {
|
||||||
layout_info.glyphs.clear();
|
layout_info.glyphs.clear();
|
||||||
layout_info.size = Default::default();
|
layout_info.size = Default::default();
|
||||||
|
|
||||||
if sections.is_empty() {
|
// Clear this here at the focal point of text rendering to ensure the field's lifecycle has strong boundaries.
|
||||||
return Ok(());
|
computed.needs_rerender = false;
|
||||||
}
|
|
||||||
|
|
||||||
self.update_buffer(
|
// Extract font ids from the iterator while traversing it.
|
||||||
|
let mut glyph_info = core::mem::take(&mut self.glyph_info);
|
||||||
|
glyph_info.clear();
|
||||||
|
let text_spans = text_spans.inspect(|(_, _, _, style)| {
|
||||||
|
glyph_info.push((style.font.id(), style.font_smoothing));
|
||||||
|
});
|
||||||
|
|
||||||
|
let update_result = self.update_buffer(
|
||||||
fonts,
|
fonts,
|
||||||
sections
|
text_spans,
|
||||||
.iter()
|
block.linebreak,
|
||||||
.map(|section| (section.value.as_str(), §ion.style)),
|
block.justify,
|
||||||
linebreak,
|
|
||||||
bounds,
|
bounds,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
buffer,
|
computed,
|
||||||
text_alignment,
|
|
||||||
font_system,
|
font_system,
|
||||||
)?;
|
);
|
||||||
|
if let Err(err) = update_result {
|
||||||
|
self.glyph_info = glyph_info;
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = &mut computed.buffer;
|
||||||
let box_size = buffer_dimensions(buffer);
|
let box_size = buffer_dimensions(buffer);
|
||||||
|
|
||||||
buffer
|
let result = buffer
|
||||||
.layout_runs()
|
.layout_runs()
|
||||||
.flat_map(|run| {
|
.flat_map(|run| {
|
||||||
run.glyphs
|
run.glyphs
|
||||||
|
@ -238,6 +253,9 @@ impl TextPipeline {
|
||||||
})
|
})
|
||||||
.try_for_each(|(layout_glyph, line_y)| {
|
.try_for_each(|(layout_glyph, line_y)| {
|
||||||
let mut temp_glyph;
|
let mut temp_glyph;
|
||||||
|
let span_index = layout_glyph.metadata;
|
||||||
|
let font_id = glyph_info[span_index].0;
|
||||||
|
let font_smoothing = glyph_info[span_index].1;
|
||||||
|
|
||||||
let layout_glyph = if font_smoothing == FontSmoothing::None {
|
let layout_glyph = if font_smoothing == FontSmoothing::None {
|
||||||
// If font smoothing is disabled, round the glyph positions and sizes,
|
// If font smoothing is disabled, round the glyph positions and sizes,
|
||||||
|
@ -255,10 +273,7 @@ impl TextPipeline {
|
||||||
layout_glyph
|
layout_glyph
|
||||||
};
|
};
|
||||||
|
|
||||||
let section_index = layout_glyph.metadata;
|
let font_atlas_set = font_atlas_sets.sets.entry(font_id).or_default();
|
||||||
|
|
||||||
let font_handle = sections[section_index].style.font.clone_weak();
|
|
||||||
let font_atlas_set = font_atlas_sets.sets.entry(font_handle.id()).or_default();
|
|
||||||
|
|
||||||
let physical_glyph = layout_glyph.physical((0., 0.), 1.);
|
let physical_glyph = layout_glyph.physical((0., 0.), 1.);
|
||||||
|
|
||||||
|
@ -296,10 +311,16 @@ impl TextPipeline {
|
||||||
// TODO: recreate the byte index, that keeps track of where a cursor is,
|
// TODO: recreate the byte index, that keeps track of where a cursor is,
|
||||||
// when glyphs are not limited to single byte representation, relevant for #1319
|
// when glyphs are not limited to single byte representation, relevant for #1319
|
||||||
let pos_glyph =
|
let pos_glyph =
|
||||||
PositionedGlyph::new(position, glyph_size.as_vec2(), atlas_info, section_index);
|
PositionedGlyph::new(position, glyph_size.as_vec2(), atlas_info, span_index);
|
||||||
layout_info.glyphs.push(pos_glyph);
|
layout_info.glyphs.push(pos_glyph);
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
});
|
||||||
|
|
||||||
|
// Return the scratch vec.
|
||||||
|
self.glyph_info = glyph_info;
|
||||||
|
|
||||||
|
// Check result.
|
||||||
|
result?;
|
||||||
|
|
||||||
layout_info.size = box_size;
|
layout_info.size = box_size;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -310,32 +331,34 @@ impl TextPipeline {
|
||||||
/// Produces a [`TextMeasureInfo`] which can be used by a layout system
|
/// Produces a [`TextMeasureInfo`] which can be used by a layout system
|
||||||
/// to measure the text area on demand.
|
/// to measure the text area on demand.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn create_text_measure(
|
pub fn create_text_measure<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
fonts: &Assets<Font>,
|
fonts: &Assets<Font>,
|
||||||
sections: &[TextSection],
|
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>,
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
linebreak: LineBreak,
|
block: &TextBlock,
|
||||||
buffer: &mut CosmicBuffer,
|
computed: &mut ComputedTextBlock,
|
||||||
text_alignment: JustifyText,
|
|
||||||
font_system: &mut CosmicFontSystem,
|
font_system: &mut CosmicFontSystem,
|
||||||
) -> Result<TextMeasureInfo, TextError> {
|
) -> Result<TextMeasureInfo, TextError> {
|
||||||
const MIN_WIDTH_CONTENT_BOUNDS: TextBounds = TextBounds::new_horizontal(0.0);
|
const MIN_WIDTH_CONTENT_BOUNDS: TextBounds = TextBounds::new_horizontal(0.0);
|
||||||
|
|
||||||
|
// Clear this here at the focal point of measured text rendering to ensure the field's lifecycle has
|
||||||
|
// strong boundaries.
|
||||||
|
computed.needs_rerender = false;
|
||||||
|
|
||||||
self.update_buffer(
|
self.update_buffer(
|
||||||
fonts,
|
fonts,
|
||||||
sections
|
text_spans,
|
||||||
.iter()
|
block.linebreak,
|
||||||
.map(|section| (section.value.as_str(), §ion.style)),
|
block.justify,
|
||||||
linebreak,
|
|
||||||
MIN_WIDTH_CONTENT_BOUNDS,
|
MIN_WIDTH_CONTENT_BOUNDS,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
buffer,
|
computed,
|
||||||
text_alignment,
|
|
||||||
font_system,
|
font_system,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let buffer = &mut computed.buffer;
|
||||||
let min_width_content_size = buffer_dimensions(buffer);
|
let min_width_content_size = buffer_dimensions(buffer);
|
||||||
|
|
||||||
let max_width_content_size = {
|
let max_width_content_size = {
|
||||||
|
@ -360,9 +383,10 @@ impl TextPipeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render information for a corresponding [`Text`](crate::Text) component.
|
/// Render information for a corresponding text block.
|
||||||
///
|
///
|
||||||
/// Contains scaled glyphs and their size. Generated via [`TextPipeline::queue_text`].
|
/// Contains scaled glyphs and their size. Generated via [`TextPipeline::queue_text`] when an entity has
|
||||||
|
/// [`TextBlock`] and [`ComputedTextBlock`] components.
|
||||||
#[derive(Component, Clone, Default, Debug, Reflect)]
|
#[derive(Component, Clone, Default, Debug, Reflect)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct TextLayoutInfo {
|
pub struct TextLayoutInfo {
|
||||||
|
@ -372,7 +396,7 @@ pub struct TextLayoutInfo {
|
||||||
pub size: Vec2,
|
pub size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Size information for a corresponding [`Text`](crate::Text) component.
|
/// Size information for a corresponding [`ComputedTextBlock`] component.
|
||||||
///
|
///
|
||||||
/// Generated via [`TextPipeline::create_text_measure`].
|
/// Generated via [`TextPipeline::create_text_measure`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -390,13 +414,15 @@ impl TextMeasureInfo {
|
||||||
pub fn compute_size(
|
pub fn compute_size(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: TextBounds,
|
bounds: TextBounds,
|
||||||
buffer: &mut Buffer,
|
computed: &mut ComputedTextBlock,
|
||||||
font_system: &mut cosmic_text::FontSystem,
|
font_system: &mut cosmic_text::FontSystem,
|
||||||
) -> Vec2 {
|
) -> Vec2 {
|
||||||
// Note that this arbitrarily adjusts the buffer layout. We assume the buffer is always 'refreshed'
|
// Note that this arbitrarily adjusts the buffer layout. We assume the buffer is always 'refreshed'
|
||||||
// whenever a canonical state is required.
|
// whenever a canonical state is required.
|
||||||
buffer.set_size(font_system, bounds.width, bounds.height);
|
computed
|
||||||
buffer_dimensions(buffer)
|
.buffer
|
||||||
|
.set_size(font_system, bounds.width, bounds.height);
|
||||||
|
buffer_dimensions(&computed.buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
use bevy_asset::Handle;
|
|
||||||
use bevy_color::Color;
|
|
||||||
use bevy_derive::{Deref, DerefMut};
|
|
||||||
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
|
|
||||||
use bevy_reflect::prelude::*;
|
|
||||||
use bevy_utils::default;
|
|
||||||
use cosmic_text::{Buffer, Metrics};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::Font;
|
|
||||||
pub use cosmic_text::{
|
pub use cosmic_text::{
|
||||||
self, FamilyOwned as FontFamily, Stretch as FontStretch, Style as FontStyle,
|
self, FamilyOwned as FontFamily, Stretch as FontStretch, Style as FontStyle,
|
||||||
Weight as FontWeight,
|
Weight as FontWeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{Font, TextLayoutInfo, TextSpanAccess, TextSpanComponent};
|
||||||
|
use bevy_asset::Handle;
|
||||||
|
use bevy_color::Color;
|
||||||
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
|
||||||
|
use bevy_hierarchy::{Children, Parent};
|
||||||
|
use bevy_reflect::prelude::*;
|
||||||
|
use bevy_utils::warn_once;
|
||||||
|
use cosmic_text::{Buffer, Metrics};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
/// Wrapper for [`cosmic_text::Buffer`]
|
/// Wrapper for [`cosmic_text::Buffer`]
|
||||||
#[derive(Component, Deref, DerefMut, Debug, Clone)]
|
#[derive(Deref, DerefMut, Debug, Clone)]
|
||||||
pub struct CosmicBuffer(pub Buffer);
|
pub struct CosmicBuffer(pub Buffer);
|
||||||
|
|
||||||
impl Default for CosmicBuffer {
|
impl Default for CosmicBuffer {
|
||||||
|
@ -23,160 +25,199 @@ impl Default for CosmicBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component that is the entry point for rendering text.
|
/// A sub-entity of a [`TextBlock`].
|
||||||
///
|
///
|
||||||
/// It contains all of the text value and styling information.
|
/// Returned by [`ComputedTextBlock::entities`].
|
||||||
#[derive(Component, Debug, Clone, Default, Reflect)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct TextEntity {
|
||||||
|
/// The entity.
|
||||||
|
pub entity: Entity,
|
||||||
|
/// Records the hierarchy depth of the entity within a `TextBlock`.
|
||||||
|
pub depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computed information for a [`TextBlock`].
|
||||||
|
///
|
||||||
|
/// Automatically updated by 2d and UI text systems.
|
||||||
|
#[derive(Component, Debug, Clone)]
|
||||||
|
pub struct ComputedTextBlock {
|
||||||
|
/// Buffer for managing text layout and creating [`TextLayoutInfo`].
|
||||||
|
///
|
||||||
|
/// This is private because buffer contents are always refreshed from ECS state when writing glyphs to
|
||||||
|
/// `TextLayoutInfo`. If you want to control the buffer contents manually or use the `cosmic-text`
|
||||||
|
/// editor, then you need to not use `TextBlock` and instead manually implement the conversion to
|
||||||
|
/// `TextLayoutInfo`.
|
||||||
|
pub(crate) buffer: CosmicBuffer,
|
||||||
|
/// Entities for all text spans in the block, including the root-level text.
|
||||||
|
///
|
||||||
|
/// The [`TextEntity::depth`] field can be used to reconstruct the hierarchy.
|
||||||
|
pub(crate) entities: SmallVec<[TextEntity; 1]>,
|
||||||
|
/// Flag set when any change has been made to this block that should cause it to be rerendered.
|
||||||
|
///
|
||||||
|
/// Includes:
|
||||||
|
/// - [`TextBlock`] changes.
|
||||||
|
/// - [`TextStyle`] or `Text2d`/`Text`/`TextSpan` changes anywhere in the block's entity hierarchy.
|
||||||
|
// TODO: This encompasses both structural changes like font size or justification and non-structural
|
||||||
|
// changes like text color and font smoothing. This field currently causes UI to 'remeasure' text, even if
|
||||||
|
// the actual changes are non-structural and can be handled by only rerendering and not remeasuring. A full
|
||||||
|
// solution would probably require splitting TextBlock and TextStyle into structural/non-structural
|
||||||
|
// components for more granular change detection. A cost/benefit analysis is needed.
|
||||||
|
pub(crate) needs_rerender: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComputedTextBlock {
|
||||||
|
/// Accesses entities in this block.
|
||||||
|
///
|
||||||
|
/// Can be used to look up [`TextStyle`] components for glyphs in [`TextLayoutInfo`] using the `span_index`
|
||||||
|
/// stored there.
|
||||||
|
pub fn entities(&self) -> &[TextEntity] {
|
||||||
|
&self.entities
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates if the text needs to be refreshed in [`TextLayoutInfo`].
|
||||||
|
///
|
||||||
|
/// Updated automatically by [`detect_text_needs_rerender`] and cleared
|
||||||
|
/// by [`TextPipeline`](crate::TextPipeline) methods.
|
||||||
|
pub fn needs_rerender(&self) -> bool {
|
||||||
|
self.needs_rerender
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ComputedTextBlock {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: CosmicBuffer::default(),
|
||||||
|
entities: SmallVec::default(),
|
||||||
|
needs_rerender: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Component with text format settings for a block of text.
|
||||||
|
///
|
||||||
|
/// A block of text is composed of text spans, which each have a separate string value and [`TextStyle`]. Text
|
||||||
|
/// spans associated with a text block are collected into [`ComputedTextBlock`] for layout, and then inserted
|
||||||
|
/// to [`TextLayoutInfo`] for rendering.
|
||||||
|
///
|
||||||
|
/// See [`Text2d`](crate::Text2d) for the core component of 2d text, and `Text` in `bevy_ui` for UI text.
|
||||||
|
#[derive(Component, Debug, Copy, Clone, Default, Reflect)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct Text {
|
#[require(ComputedTextBlock, TextLayoutInfo)]
|
||||||
/// The text's sections
|
pub struct TextBlock {
|
||||||
pub sections: Vec<TextSection>,
|
|
||||||
/// The text's internal alignment.
|
/// The text's internal alignment.
|
||||||
/// Should not affect its position within a container.
|
/// Should not affect its position within a container.
|
||||||
pub justify: JustifyText,
|
pub justify: JustifyText,
|
||||||
/// How the text should linebreak when running out of the bounds determined by `max_size`
|
/// How the text should linebreak when running out of the bounds determined by `max_size`.
|
||||||
pub linebreak: LineBreak,
|
pub linebreak: LineBreak,
|
||||||
/// The antialiasing method to use when rendering text.
|
|
||||||
pub font_smoothing: FontSmoothing,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Text {
|
impl TextBlock {
|
||||||
/// Constructs a [`Text`] with a single section.
|
/// Makes a new [`TextBlock`].
|
||||||
///
|
pub const fn new(justify: JustifyText, linebreak: LineBreak) -> Self {
|
||||||
/// ```
|
Self { justify, linebreak }
|
||||||
/// # use bevy_asset::Handle;
|
|
||||||
/// # use bevy_color::Color;
|
|
||||||
/// # use bevy_text::{Font, Text, TextStyle, JustifyText};
|
|
||||||
/// #
|
|
||||||
/// # let font_handle: Handle<Font> = Default::default();
|
|
||||||
/// #
|
|
||||||
/// // Basic usage.
|
|
||||||
/// let hello_world = Text::from_section(
|
|
||||||
/// // Accepts a String or any type that converts into a String, such as &str.
|
|
||||||
/// "hello world!",
|
|
||||||
/// TextStyle {
|
|
||||||
/// font: font_handle.clone().into(),
|
|
||||||
/// font_size: 60.0,
|
|
||||||
/// color: Color::WHITE,
|
|
||||||
/// },
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// let hello_bevy = Text::from_section(
|
|
||||||
/// "hello world\nand bevy!",
|
|
||||||
/// TextStyle {
|
|
||||||
/// font: font_handle.into(),
|
|
||||||
/// font_size: 60.0,
|
|
||||||
/// color: Color::WHITE,
|
|
||||||
/// },
|
|
||||||
/// ) // You can still add text justifaction.
|
|
||||||
/// .with_justify(JustifyText::Center);
|
|
||||||
/// ```
|
|
||||||
pub fn from_section(value: impl Into<String>, style: TextStyle) -> Self {
|
|
||||||
Self {
|
|
||||||
sections: vec![TextSection::new(value, style)],
|
|
||||||
..default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a [`Text`] from a list of sections.
|
/// Makes a new [`TextBlock`] with the specified [`JustifyText`].
|
||||||
///
|
pub fn new_with_justify(justify: JustifyText) -> Self {
|
||||||
/// ```
|
Self::default().with_justify(justify)
|
||||||
/// # use bevy_asset::Handle;
|
|
||||||
/// # use bevy_color::Color;
|
|
||||||
/// # use bevy_color::palettes::basic::{RED, BLUE};
|
|
||||||
/// # use bevy_text::{Font, Text, TextStyle, TextSection};
|
|
||||||
/// #
|
|
||||||
/// # let font_handle: Handle<Font> = Default::default();
|
|
||||||
/// #
|
|
||||||
/// let hello_world = Text::from_sections([
|
|
||||||
/// TextSection::new(
|
|
||||||
/// "Hello, ",
|
|
||||||
/// TextStyle {
|
|
||||||
/// font: font_handle.clone().into(),
|
|
||||||
/// font_size: 60.0,
|
|
||||||
/// color: BLUE.into(),
|
|
||||||
/// },
|
|
||||||
/// ),
|
|
||||||
/// TextSection::new(
|
|
||||||
/// "World!",
|
|
||||||
/// TextStyle {
|
|
||||||
/// font: font_handle.into(),
|
|
||||||
/// font_size: 60.0,
|
|
||||||
/// color: RED.into(),
|
|
||||||
/// },
|
|
||||||
/// ),
|
|
||||||
/// ]);
|
|
||||||
/// ```
|
|
||||||
pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
|
|
||||||
Self {
|
|
||||||
sections: sections.into_iter().collect(),
|
|
||||||
..default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns this [`Text`] with a new [`JustifyText`].
|
/// Makes a new [`TextBlock`] with the specified [`LineBreak`].
|
||||||
|
pub fn new_with_linebreak(linebreak: LineBreak) -> Self {
|
||||||
|
Self::default().with_linebreak(linebreak)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a new [`TextBlock`] with soft wrapping disabled.
|
||||||
|
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
|
||||||
|
pub fn new_with_no_wrap() -> Self {
|
||||||
|
Self::default().with_no_wrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this [`TextBlock`] with the specified [`JustifyText`].
|
||||||
pub const fn with_justify(mut self, justify: JustifyText) -> Self {
|
pub const fn with_justify(mut self, justify: JustifyText) -> Self {
|
||||||
self.justify = justify;
|
self.justify = justify;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns this [`Text`] with soft wrapping disabled.
|
/// Returns this [`TextBlock`] with the specified [`LineBreak`].
|
||||||
|
pub const fn with_linebreak(mut self, linebreak: LineBreak) -> Self {
|
||||||
|
self.linebreak = linebreak;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this [`TextBlock`] with soft wrapping disabled.
|
||||||
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
|
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
|
||||||
pub const fn with_no_wrap(mut self) -> Self {
|
pub const fn with_no_wrap(mut self) -> Self {
|
||||||
self.linebreak = LineBreak::NoWrap;
|
self.linebreak = LineBreak::NoWrap;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns this [`Text`] with the specified [`FontSmoothing`].
|
/// A span of UI text in a tree of spans under an entity with [`TextBlock`], such as `Text` or `Text2d`.
|
||||||
pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self {
|
///
|
||||||
self.font_smoothing = font_smoothing;
|
/// Spans are collected in hierarchy traversal order into a [`ComputedTextBlock`] for layout.
|
||||||
self
|
///
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
# use bevy_asset::Handle;
|
||||||
|
# use bevy_color::Color;
|
||||||
|
# use bevy_color::palettes::basic::{RED, BLUE};
|
||||||
|
# use bevy_ecs::World;
|
||||||
|
# use bevy_text::{Font, TextBlock, TextStyle, TextSection};
|
||||||
|
|
||||||
|
# let font_handle: Handle<Font> = Default::default();
|
||||||
|
# let mut world = World::default();
|
||||||
|
#
|
||||||
|
world.spawn((
|
||||||
|
TextBlock::default(),
|
||||||
|
TextStyle {
|
||||||
|
font: font_handle.clone().into(),
|
||||||
|
font_size: 60.0,
|
||||||
|
color: BLUE.into(),
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.with_child((
|
||||||
|
TextSpan::new("Hello!"),
|
||||||
|
TextStyle {
|
||||||
|
font: font_handle.into(),
|
||||||
|
font_size: 60.0,
|
||||||
|
color: RED.into(),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
#[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)]
|
||||||
|
#[reflect(Component, Default, Debug)]
|
||||||
|
#[require(TextStyle)]
|
||||||
|
pub struct TextSpan(pub String);
|
||||||
|
|
||||||
|
impl TextSpan {
|
||||||
|
/// Makes a new text span component.
|
||||||
|
pub fn new(text: impl Into<String>) -> Self {
|
||||||
|
Self(text.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains the value of the text in a section and how it should be styled.
|
impl TextSpanComponent for TextSpan {}
|
||||||
#[derive(Debug, Default, Clone, Reflect)]
|
|
||||||
#[reflect(Default)]
|
|
||||||
pub struct TextSection {
|
|
||||||
/// The content (in `String` form) of the text in the section.
|
|
||||||
pub value: String,
|
|
||||||
/// The style of the text in the section, including the font face, font size, and color.
|
|
||||||
pub style: TextStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextSection {
|
impl TextSpanAccess for TextSpan {
|
||||||
/// Create a new [`TextSection`].
|
fn read_span(&self) -> &str {
|
||||||
pub fn new(value: impl Into<String>, style: TextStyle) -> Self {
|
self.as_str()
|
||||||
Self {
|
|
||||||
value: value.into(),
|
|
||||||
style,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
fn write_span(&mut self) -> &mut String {
|
||||||
/// Create an empty [`TextSection`] from a style. Useful when the value will be set dynamically.
|
&mut *self
|
||||||
pub const fn from_style(style: TextStyle) -> Self {
|
|
||||||
Self {
|
|
||||||
value: String::new(),
|
|
||||||
style,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for TextSection {
|
impl From<&str> for TextSpan {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
Self {
|
Self(String::from(value))
|
||||||
value: value.into(),
|
|
||||||
..default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for TextSection {
|
impl From<String> for TextSpan {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
Self {
|
Self(value)
|
||||||
value,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,9 +257,10 @@ impl From<JustifyText> for cosmic_text::Align {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Reflect)]
|
/// `TextStyle` determines the style of a text span within a [`TextBlock`], specifically
|
||||||
/// `TextStyle` determines the style of the text in a section, specifically
|
|
||||||
/// the font face, the font size, and the color.
|
/// the font face, the font size, and the color.
|
||||||
|
#[derive(Component, Clone, Debug, Reflect)]
|
||||||
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct TextStyle {
|
pub struct TextStyle {
|
||||||
/// The specific font face to use, as a `Handle` to a [`Font`] asset.
|
/// The specific font face to use, as a `Handle` to a [`Font`] asset.
|
||||||
///
|
///
|
||||||
|
@ -238,6 +280,16 @@ pub struct TextStyle {
|
||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
/// The color of the text for this section.
|
/// The color of the text for this section.
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
|
/// The antialiasing method to use when rendering text.
|
||||||
|
pub font_smoothing: FontSmoothing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextStyle {
|
||||||
|
/// Returns this [`TextBlock`] with the specified [`FontSmoothing`].
|
||||||
|
pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self {
|
||||||
|
self.font_smoothing = font_smoothing;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextStyle {
|
impl Default for TextStyle {
|
||||||
|
@ -246,6 +298,7 @@ impl Default for TextStyle {
|
||||||
font: Default::default(),
|
font: Default::default(),
|
||||||
font_size: 20.0,
|
font_size: 20.0,
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
|
font_smoothing: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,3 +346,112 @@ pub enum FontSmoothing {
|
||||||
// TODO: Add subpixel antialias support
|
// TODO: Add subpixel antialias support
|
||||||
// SubpixelAntiAliased,
|
// SubpixelAntiAliased,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// System that detects changes to text blocks and sets `ComputedTextBlock::should_rerender`.
|
||||||
|
///
|
||||||
|
/// Generic over the root text component and text span component. For example, [`Text2d`](crate::Text2d)/[`TextSpan`] for
|
||||||
|
/// 2d or `Text`/[`TextSpan`] for UI.
|
||||||
|
pub fn detect_text_needs_rerender<Root: Component>(
|
||||||
|
changed_roots: Query<
|
||||||
|
Entity,
|
||||||
|
(
|
||||||
|
Or<(
|
||||||
|
Changed<Root>,
|
||||||
|
Changed<TextStyle>,
|
||||||
|
Changed<TextBlock>,
|
||||||
|
Changed<Children>,
|
||||||
|
)>,
|
||||||
|
With<Root>,
|
||||||
|
With<TextStyle>,
|
||||||
|
With<TextBlock>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
changed_spans: Query<
|
||||||
|
(Entity, Option<&Parent>, Has<TextBlock>),
|
||||||
|
(
|
||||||
|
Or<(
|
||||||
|
Changed<TextSpan>,
|
||||||
|
Changed<TextStyle>,
|
||||||
|
Changed<Children>,
|
||||||
|
Changed<Parent>, // Included to detect broken text block hierarchies.
|
||||||
|
Added<TextBlock>,
|
||||||
|
)>,
|
||||||
|
With<TextSpan>,
|
||||||
|
With<TextStyle>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
mut computed: Query<(
|
||||||
|
Option<&Parent>,
|
||||||
|
Option<&mut ComputedTextBlock>,
|
||||||
|
Has<TextSpan>,
|
||||||
|
)>,
|
||||||
|
) {
|
||||||
|
// Root entity:
|
||||||
|
// - Root component changed.
|
||||||
|
// - TextStyle on root changed.
|
||||||
|
// - TextBlock changed.
|
||||||
|
// - Root children changed (can include additions and removals).
|
||||||
|
for root in changed_roots.iter() {
|
||||||
|
let Ok((_, Some(mut computed), _)) = computed.get_mut(root) else {
|
||||||
|
warn_once!("found entity {:?} with a root text component ({}) but no ComputedTextBlock; this warning only \
|
||||||
|
prints once", root, core::any::type_name::<Root>());
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
computed.needs_rerender = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span entity:
|
||||||
|
// - Span component changed.
|
||||||
|
// - Span TextStyle changed.
|
||||||
|
// - Span children changed (can include additions and removals).
|
||||||
|
for (entity, maybe_span_parent, has_text_block) in changed_spans.iter() {
|
||||||
|
if has_text_block {
|
||||||
|
warn_once!("found entity {:?} with a TextSpan that has a TextBlock, which should only be on root \
|
||||||
|
text entities (that have {}); this warning only prints once",
|
||||||
|
entity, core::any::type_name::<Root>());
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(span_parent) = maybe_span_parent else {
|
||||||
|
warn_once!(
|
||||||
|
"found entity {:?} with a TextSpan that has no parent; it should have an ancestor \
|
||||||
|
with a root text component ({}); this warning only prints once",
|
||||||
|
entity,
|
||||||
|
core::any::type_name::<Root>()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mut parent: Entity = **span_parent;
|
||||||
|
|
||||||
|
// Search for the nearest ancestor with ComputedTextBlock.
|
||||||
|
// Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited
|
||||||
|
// is outweighed by the expense of tracking visited spans.
|
||||||
|
loop {
|
||||||
|
let Ok((maybe_parent, maybe_computed, has_span)) = computed.get_mut(parent) else {
|
||||||
|
warn_once!("found entity {:?} with a TextSpan that is part of a broken hierarchy with a Parent \
|
||||||
|
component that points at non-existent entity {:?}; this warning only prints once",
|
||||||
|
entity, parent);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
if let Some(mut computed) = maybe_computed {
|
||||||
|
computed.needs_rerender = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !has_span {
|
||||||
|
warn_once!("found entity {:?} with a TextSpan that has an ancestor ({}) that does not have a text \
|
||||||
|
span component or a ComputedTextBlock component; this warning only prints once",
|
||||||
|
entity, parent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let Some(next_parent) = maybe_parent else {
|
||||||
|
warn_once!(
|
||||||
|
"found entity {:?} with a TextSpan that has no ancestor with the root text \
|
||||||
|
component ({}); this warning only prints once",
|
||||||
|
entity,
|
||||||
|
core::any::type_name::<Root>()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
parent = **next_parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,69 +1,129 @@
|
||||||
use crate::pipeline::CosmicFontSystem;
|
use crate::pipeline::CosmicFontSystem;
|
||||||
use crate::{
|
use crate::{
|
||||||
CosmicBuffer, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, Text, TextBounds,
|
ComputedTextBlock, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, TextBlock,
|
||||||
TextError, TextLayoutInfo, TextPipeline, YAxisOrientation,
|
TextBounds, TextError, TextLayoutInfo, TextPipeline, TextReader, TextRoot, TextSpanAccess,
|
||||||
|
TextStyle, TextWriter, YAxisOrientation,
|
||||||
};
|
};
|
||||||
use bevy_asset::Assets;
|
use bevy_asset::Assets;
|
||||||
use bevy_color::LinearRgba;
|
use bevy_color::LinearRgba;
|
||||||
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::component::Component;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
bundle::Bundle,
|
|
||||||
change_detection::{DetectChanges, Ref},
|
change_detection::{DetectChanges, Ref},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
prelude::With,
|
prelude::{ReflectComponent, With},
|
||||||
query::{Changed, Without},
|
query::{Changed, Without},
|
||||||
system::{Commands, Local, Query, Res, ResMut},
|
system::{Commands, Local, Query, Res, ResMut},
|
||||||
};
|
};
|
||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
|
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||||
use bevy_render::sync_world::TemporaryRenderEntity;
|
use bevy_render::sync_world::TemporaryRenderEntity;
|
||||||
|
use bevy_render::view::Visibility;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
primitives::Aabb,
|
primitives::Aabb,
|
||||||
texture::Image,
|
texture::Image,
|
||||||
view::{InheritedVisibility, NoFrustumCulling, ViewVisibility, Visibility},
|
view::{NoFrustumCulling, ViewVisibility},
|
||||||
Extract,
|
Extract,
|
||||||
};
|
};
|
||||||
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, SpriteSource, TextureAtlasLayout};
|
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, SpriteSource, TextureAtlasLayout};
|
||||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
use bevy_transform::components::Transform;
|
||||||
|
use bevy_transform::prelude::GlobalTransform;
|
||||||
use bevy_utils::HashSet;
|
use bevy_utils::HashSet;
|
||||||
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
|
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
|
||||||
|
|
||||||
/// The bundle of components needed to draw text in a 2D scene via a `Camera2d`.
|
/// The top-level 2D text component.
|
||||||
|
///
|
||||||
|
/// Adding `Text2d` to an entity will pull in required components for setting up 2d text.
|
||||||
/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/text2d.rs)
|
/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/text2d.rs)
|
||||||
#[derive(Bundle, Clone, Debug, Default)]
|
///
|
||||||
pub struct Text2dBundle {
|
/// The string in this component is the first 'text span' in a hierarchy of text spans that are collected into
|
||||||
/// Contains the text.
|
/// a [`TextBlock`]. See [`TextSpan`](crate::TextSpan) for the component used by children of entities with [`Text2d`].
|
||||||
///
|
///
|
||||||
/// With `Text2dBundle` the alignment field of `Text` only affects the internal alignment of a block of text and not its
|
/// With `Text2d` the `justify` field of [`TextBlock`] only affects the internal alignment of a block of text and not its
|
||||||
/// relative position which is controlled by the `Anchor` component.
|
/// relative position, which is controlled by the [`Anchor`] component.
|
||||||
/// This means that for a block of text consisting of only one line that doesn't wrap, the `alignment` field will have no effect.
|
/// This means that for a block of text consisting of only one line that doesn't wrap, the `justify` field will have no effect.
|
||||||
pub text: Text,
|
///
|
||||||
/// Cached buffer for layout with cosmic-text
|
/*
|
||||||
pub buffer: CosmicBuffer,
|
```
|
||||||
/// How the text is positioned relative to its transform.
|
# use bevy_asset::Handle;
|
||||||
///
|
# use bevy_color::Color;
|
||||||
/// `text_anchor` does not affect the internal alignment of the block of text, only
|
# use bevy_color::palettes::basic::BLUE;
|
||||||
/// its position.
|
# use bevy_ecs::World;
|
||||||
pub text_anchor: Anchor,
|
# use bevy_text::{Font, JustifyText, Text2d, TextBlock, TextStyle};
|
||||||
/// The maximum width and height of the text.
|
#
|
||||||
pub text_2d_bounds: TextBounds,
|
# let font_handle: Handle<Font> = Default::default();
|
||||||
/// The transform of the text.
|
# let mut world = World::default();
|
||||||
pub transform: Transform,
|
#
|
||||||
/// The global transform of the text.
|
// Basic usage.
|
||||||
pub global_transform: GlobalTransform,
|
world.spawn(Text2d::new("hello world!"));
|
||||||
/// The visibility properties of the text.
|
|
||||||
pub visibility: Visibility,
|
// With non-default style.
|
||||||
/// Inherited visibility of an entity.
|
world.spawn((
|
||||||
pub inherited_visibility: InheritedVisibility,
|
Text2d::new("hello world!"),
|
||||||
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
TextStyle {
|
||||||
pub view_visibility: ViewVisibility,
|
font: font_handle.clone().into(),
|
||||||
/// Contains the size of the text and its glyph's position and scale data. Generated via [`TextPipeline::queue_text`]
|
font_size: 60.0,
|
||||||
pub text_layout_info: TextLayoutInfo,
|
color: BLUE.into(),
|
||||||
/// Marks that this is a [`SpriteSource`].
|
}
|
||||||
///
|
));
|
||||||
/// This is needed for visibility computation to work properly.
|
|
||||||
pub sprite_source: SpriteSource,
|
// With text justification.
|
||||||
|
world.spawn((
|
||||||
|
Text2d::new("hello world\nand bevy!"),
|
||||||
|
TextBlock::new_with_justify(JustifyText::Center)
|
||||||
|
));
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)]
|
||||||
|
#[reflect(Component, Default, Debug)]
|
||||||
|
#[require(
|
||||||
|
TextBlock,
|
||||||
|
TextStyle,
|
||||||
|
TextBounds,
|
||||||
|
Anchor,
|
||||||
|
SpriteSource,
|
||||||
|
Visibility,
|
||||||
|
Transform
|
||||||
|
)]
|
||||||
|
pub struct Text2d(pub String);
|
||||||
|
|
||||||
|
impl Text2d {
|
||||||
|
/// Makes a new 2d text component.
|
||||||
|
pub fn new(text: impl Into<String>) -> Self {
|
||||||
|
Self(text.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextRoot for Text2d {}
|
||||||
|
|
||||||
|
impl TextSpanAccess for Text2d {
|
||||||
|
fn read_span(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
fn write_span(&mut self) -> &mut String {
|
||||||
|
&mut *self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Text2d {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self(String::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Text2d {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 2d alias for [`TextReader`].
|
||||||
|
pub type TextReader2d<'w, 's> = TextReader<'w, 's, Text2d>;
|
||||||
|
|
||||||
|
/// 2d alias for [`TextWriter`].
|
||||||
|
pub type TextWriter2d<'w, 's> = TextWriter<'w, 's, Text2d>;
|
||||||
|
|
||||||
/// This system extracts the sprites from the 2D text components and adds them to the
|
/// This system extracts the sprites from the 2D text components and adds them to the
|
||||||
/// "render world".
|
/// "render world".
|
||||||
pub fn extract_text2d_sprite(
|
pub fn extract_text2d_sprite(
|
||||||
|
@ -75,12 +135,13 @@ pub fn extract_text2d_sprite(
|
||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ViewVisibility,
|
&ViewVisibility,
|
||||||
&Text,
|
&ComputedTextBlock,
|
||||||
&TextLayoutInfo,
|
&TextLayoutInfo,
|
||||||
&Anchor,
|
&Anchor,
|
||||||
&GlobalTransform,
|
&GlobalTransform,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
|
text_styles: Extract<Query<&TextStyle>>,
|
||||||
) {
|
) {
|
||||||
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
|
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
|
||||||
let scale_factor = windows
|
let scale_factor = windows
|
||||||
|
@ -89,8 +150,14 @@ pub fn extract_text2d_sprite(
|
||||||
.unwrap_or(1.0);
|
.unwrap_or(1.0);
|
||||||
let scaling = GlobalTransform::from_scale(Vec2::splat(scale_factor.recip()).extend(1.));
|
let scaling = GlobalTransform::from_scale(Vec2::splat(scale_factor.recip()).extend(1.));
|
||||||
|
|
||||||
for (original_entity, view_visibility, text, text_layout_info, anchor, global_transform) in
|
for (
|
||||||
text2d_query.iter()
|
original_entity,
|
||||||
|
view_visibility,
|
||||||
|
computed_block,
|
||||||
|
text_layout_info,
|
||||||
|
anchor,
|
||||||
|
global_transform,
|
||||||
|
) in text2d_query.iter()
|
||||||
{
|
{
|
||||||
if !view_visibility.get() {
|
if !view_visibility.get() {
|
||||||
continue;
|
continue;
|
||||||
|
@ -102,17 +169,26 @@ pub fn extract_text2d_sprite(
|
||||||
* GlobalTransform::from_translation(alignment_translation.extend(0.))
|
* GlobalTransform::from_translation(alignment_translation.extend(0.))
|
||||||
* scaling;
|
* scaling;
|
||||||
let mut color = LinearRgba::WHITE;
|
let mut color = LinearRgba::WHITE;
|
||||||
let mut current_section = usize::MAX;
|
let mut current_span = usize::MAX;
|
||||||
for PositionedGlyph {
|
for PositionedGlyph {
|
||||||
position,
|
position,
|
||||||
atlas_info,
|
atlas_info,
|
||||||
section_index,
|
span_index,
|
||||||
..
|
..
|
||||||
} in &text_layout_info.glyphs
|
} in &text_layout_info.glyphs
|
||||||
{
|
{
|
||||||
if *section_index != current_section {
|
if *span_index != current_span {
|
||||||
color = LinearRgba::from(text.sections[*section_index].style.color);
|
color = text_styles
|
||||||
current_section = *section_index;
|
.get(
|
||||||
|
computed_block
|
||||||
|
.entities()
|
||||||
|
.get(*span_index)
|
||||||
|
.map(|t| t.entity)
|
||||||
|
.unwrap_or(Entity::PLACEHOLDER),
|
||||||
|
)
|
||||||
|
.map(|style| LinearRgba::from(style.color))
|
||||||
|
.unwrap_or_default();
|
||||||
|
current_span = *span_index;
|
||||||
}
|
}
|
||||||
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
||||||
|
|
||||||
|
@ -154,11 +230,12 @@ pub fn update_text2d_layout(
|
||||||
mut text_pipeline: ResMut<TextPipeline>,
|
mut text_pipeline: ResMut<TextPipeline>,
|
||||||
mut text_query: Query<(
|
mut text_query: Query<(
|
||||||
Entity,
|
Entity,
|
||||||
Ref<Text>,
|
Ref<TextBlock>,
|
||||||
Ref<TextBounds>,
|
Ref<TextBounds>,
|
||||||
&mut TextLayoutInfo,
|
&mut TextLayoutInfo,
|
||||||
&mut CosmicBuffer,
|
&mut ComputedTextBlock,
|
||||||
)>,
|
)>,
|
||||||
|
mut text_reader: TextReader2d,
|
||||||
mut font_system: ResMut<CosmicFontSystem>,
|
mut font_system: ResMut<CosmicFontSystem>,
|
||||||
mut swash_cache: ResMut<SwashCache>,
|
mut swash_cache: ResMut<SwashCache>,
|
||||||
) {
|
) {
|
||||||
|
@ -173,10 +250,14 @@ pub fn update_text2d_layout(
|
||||||
|
|
||||||
let inverse_scale_factor = scale_factor.recip();
|
let inverse_scale_factor = scale_factor.recip();
|
||||||
|
|
||||||
for (entity, text, bounds, text_layout_info, mut buffer) in &mut text_query {
|
for (entity, block, bounds, text_layout_info, mut computed) in &mut text_query {
|
||||||
if factor_changed || text.is_changed() || bounds.is_changed() || queue.remove(&entity) {
|
if factor_changed
|
||||||
|
|| computed.needs_rerender()
|
||||||
|
|| bounds.is_changed()
|
||||||
|
|| queue.remove(&entity)
|
||||||
|
{
|
||||||
let text_bounds = TextBounds {
|
let text_bounds = TextBounds {
|
||||||
width: if text.linebreak == LineBreak::NoWrap {
|
width: if block.linebreak == LineBreak::NoWrap {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
bounds.width.map(|width| scale_value(width, scale_factor))
|
bounds.width.map(|width| scale_value(width, scale_factor))
|
||||||
|
@ -190,17 +271,15 @@ pub fn update_text2d_layout(
|
||||||
match text_pipeline.queue_text(
|
match text_pipeline.queue_text(
|
||||||
text_layout_info,
|
text_layout_info,
|
||||||
&fonts,
|
&fonts,
|
||||||
&text.sections,
|
text_reader.iter(entity),
|
||||||
scale_factor.into(),
|
scale_factor.into(),
|
||||||
text.justify,
|
&block,
|
||||||
text.linebreak,
|
|
||||||
text.font_smoothing,
|
|
||||||
text_bounds,
|
text_bounds,
|
||||||
&mut font_atlas_sets,
|
&mut font_atlas_sets,
|
||||||
&mut texture_atlases,
|
&mut texture_atlases,
|
||||||
&mut textures,
|
&mut textures,
|
||||||
YAxisOrientation::BottomToTop,
|
YAxisOrientation::BottomToTop,
|
||||||
buffer.as_mut(),
|
computed.as_mut(),
|
||||||
&mut font_system,
|
&mut font_system,
|
||||||
&mut swash_cache,
|
&mut swash_cache,
|
||||||
) {
|
) {
|
||||||
|
@ -265,7 +344,8 @@ mod tests {
|
||||||
use bevy_app::{App, Update};
|
use bevy_app::{App, Update};
|
||||||
use bevy_asset::{load_internal_binary_asset, Handle};
|
use bevy_asset::{load_internal_binary_asset, Handle};
|
||||||
use bevy_ecs::{event::Events, schedule::IntoSystemConfigs};
|
use bevy_ecs::{event::Events, schedule::IntoSystemConfigs};
|
||||||
use bevy_utils::default;
|
|
||||||
|
use crate::{detect_text_needs_rerender, TextIterScratch};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -282,12 +362,15 @@ mod tests {
|
||||||
.init_resource::<TextPipeline>()
|
.init_resource::<TextPipeline>()
|
||||||
.init_resource::<CosmicFontSystem>()
|
.init_resource::<CosmicFontSystem>()
|
||||||
.init_resource::<SwashCache>()
|
.init_resource::<SwashCache>()
|
||||||
|
.init_resource::<TextIterScratch>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
|
detect_text_needs_rerender::<Text2d>,
|
||||||
update_text2d_layout,
|
update_text2d_layout,
|
||||||
calculate_bounds_text2d.after(update_text2d_layout),
|
calculate_bounds_text2d,
|
||||||
),
|
)
|
||||||
|
.chain(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// A font is needed to ensure the text is laid out with an actual size.
|
// A font is needed to ensure the text is laid out with an actual size.
|
||||||
|
@ -298,13 +381,7 @@ mod tests {
|
||||||
|bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() }
|
|bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() }
|
||||||
);
|
);
|
||||||
|
|
||||||
let entity = app
|
let entity = app.world_mut().spawn(Text2d::new(FIRST_TEXT)).id();
|
||||||
.world_mut()
|
|
||||||
.spawn((Text2dBundle {
|
|
||||||
text: Text::from_section(FIRST_TEXT, default()),
|
|
||||||
..default()
|
|
||||||
},))
|
|
||||||
.id();
|
|
||||||
|
|
||||||
(app, entity)
|
(app, entity)
|
||||||
}
|
}
|
||||||
|
@ -356,8 +433,8 @@ mod tests {
|
||||||
.get_entity_mut(entity)
|
.get_entity_mut(entity)
|
||||||
.expect("Could not find entity");
|
.expect("Could not find entity");
|
||||||
*entity_ref
|
*entity_ref
|
||||||
.get_mut::<Text>()
|
.get_mut::<Text2d>()
|
||||||
.expect("Missing Text on entity") = Text::from_section(SECOND_TEXT, default());
|
.expect("Missing Text2d on entity") = Text2d::new(SECOND_TEXT);
|
||||||
|
|
||||||
// Recomputes the AABB.
|
// Recomputes the AABB.
|
||||||
app.update();
|
app.update();
|
||||||
|
|
381
crates/bevy_text/src/text_access.rs
Normal file
381
crates/bevy_text/src/text_access.rs
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
use bevy_ecs::{
|
||||||
|
prelude::*,
|
||||||
|
system::{Query, SystemParam},
|
||||||
|
};
|
||||||
|
use bevy_hierarchy::Children;
|
||||||
|
|
||||||
|
use crate::{TextSpan, TextStyle};
|
||||||
|
|
||||||
|
/// Helper trait for using the [`TextReader`] and [`TextWriter`] system params.
|
||||||
|
pub trait TextSpanAccess: Component {
|
||||||
|
/// Gets the text span's string.
|
||||||
|
fn read_span(&self) -> &str;
|
||||||
|
/// Gets mutable reference to the text span's string.
|
||||||
|
fn write_span(&mut self) -> &mut String;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper trait for the root text component in a text block.
|
||||||
|
pub trait TextRoot: TextSpanAccess + From<String> {}
|
||||||
|
|
||||||
|
/// Helper trait for the text span components in a text block.
|
||||||
|
pub trait TextSpanComponent: TextSpanAccess + From<String> {}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub(crate) struct TextIterScratch {
|
||||||
|
stack: Vec<(&'static Children, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextIterScratch {
|
||||||
|
fn take<'a>(&mut self) -> Vec<(&'a Children, usize)> {
|
||||||
|
core::mem::take(&mut self.stack)
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| -> (&Children, usize) { unreachable!() })
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recover(&mut self, mut stack: Vec<(&Children, usize)>) {
|
||||||
|
stack.clear();
|
||||||
|
self.stack = stack
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| -> (&'static Children, usize) { unreachable!() })
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System parameter for reading text spans in a [`TextBlock`](crate::TextBlock).
|
||||||
|
///
|
||||||
|
/// `R` is the root text component, and `S` is the text span component on children.
|
||||||
|
#[derive(SystemParam)]
|
||||||
|
pub struct TextReader<'w, 's, R: TextRoot> {
|
||||||
|
// This is a local to avoid system ambiguities when TextReaders run in parallel.
|
||||||
|
scratch: Local<'s, TextIterScratch>,
|
||||||
|
roots: Query<'w, 's, (&'static R, &'static TextStyle, Option<&'static Children>)>,
|
||||||
|
spans: Query<
|
||||||
|
'w,
|
||||||
|
's,
|
||||||
|
(
|
||||||
|
&'static TextSpan,
|
||||||
|
&'static TextStyle,
|
||||||
|
Option<&'static Children>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, R: TextRoot> TextReader<'w, 's, R> {
|
||||||
|
/// Returns an iterator over text spans in a text block, starting with the root entity.
|
||||||
|
pub fn iter(&mut self, root_entity: Entity) -> TextSpanIter<R> {
|
||||||
|
let stack = self.scratch.take();
|
||||||
|
|
||||||
|
TextSpanIter {
|
||||||
|
scratch: &mut self.scratch,
|
||||||
|
root_entity: Some(root_entity),
|
||||||
|
stack,
|
||||||
|
roots: &self.roots,
|
||||||
|
spans: &self.spans,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a text span within a text block at a specific index in the flattened span list.
|
||||||
|
pub fn get(
|
||||||
|
&mut self,
|
||||||
|
root_entity: Entity,
|
||||||
|
index: usize,
|
||||||
|
) -> Option<(Entity, usize, &str, &TextStyle)> {
|
||||||
|
self.iter(root_entity).nth(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the text value of a text span within a text block at a specific index in the flattened span list.
|
||||||
|
pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option<&str> {
|
||||||
|
self.get(root_entity, index).map(|(_, _, text, _)| text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the [`TextStyle`] of a text span within a text block at a specific index in the flattened span list.
|
||||||
|
pub fn get_style(&mut self, root_entity: Entity, index: usize) -> Option<&TextStyle> {
|
||||||
|
self.get(root_entity, index).map(|(_, _, _, style)| style)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the text value of a text span within a text block at a specific index in the flattened span list.
|
||||||
|
///
|
||||||
|
/// Panics if there is no span at the requested index.
|
||||||
|
pub fn text(&mut self, root_entity: Entity, index: usize) -> &str {
|
||||||
|
self.get_text(root_entity, index).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the [`TextStyle`] of a text span within a text block at a specific index in the flattened span list.
|
||||||
|
///
|
||||||
|
/// Panics if there is no span at the requested index.
|
||||||
|
pub fn style(&mut self, root_entity: Entity, index: usize) -> &TextStyle {
|
||||||
|
self.get_style(root_entity, index).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator returned by [`TextReader::iter`].
|
||||||
|
///
|
||||||
|
/// Iterates all spans in a text block according to hierarchy traversal order.
|
||||||
|
/// Does *not* flatten interspersed ghost nodes. Only contiguous spans are traversed.
|
||||||
|
// TODO: Use this iterator design in UiChildrenIter to reduce allocations.
|
||||||
|
pub struct TextSpanIter<'a, R: TextRoot> {
|
||||||
|
scratch: &'a mut TextIterScratch,
|
||||||
|
root_entity: Option<Entity>,
|
||||||
|
/// Stack of (children, next index into children).
|
||||||
|
stack: Vec<(&'a Children, usize)>,
|
||||||
|
roots: &'a Query<'a, 'a, (&'static R, &'static TextStyle, Option<&'static Children>)>,
|
||||||
|
spans: &'a Query<
|
||||||
|
'a,
|
||||||
|
'a,
|
||||||
|
(
|
||||||
|
&'static TextSpan,
|
||||||
|
&'static TextStyle,
|
||||||
|
Option<&'static Children>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> {
|
||||||
|
/// Item = (entity in text block, hierarchy depth in the block, span text, span style).
|
||||||
|
type Item = (Entity, usize, &'a str, &'a TextStyle);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// Root
|
||||||
|
if let Some(root_entity) = self.root_entity.take() {
|
||||||
|
if let Ok((text, style, maybe_children)) = self.roots.get(root_entity) {
|
||||||
|
if let Some(children) = maybe_children {
|
||||||
|
self.stack.push((children, 0));
|
||||||
|
}
|
||||||
|
return Some((root_entity, 0, text.read_span(), style));
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span
|
||||||
|
loop {
|
||||||
|
let (children, idx) = self.stack.last_mut()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(child) = children.get(*idx) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Increment to prep the next entity in this stack level.
|
||||||
|
*idx += 1;
|
||||||
|
|
||||||
|
let entity = *child;
|
||||||
|
let Ok((span, style, maybe_children)) = self.spans.get(entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let depth = self.stack.len();
|
||||||
|
if let Some(children) = maybe_children {
|
||||||
|
self.stack.push((children, 0));
|
||||||
|
}
|
||||||
|
return Some((entity, depth, span.read_span(), style));
|
||||||
|
}
|
||||||
|
|
||||||
|
// All children at this stack entry have been iterated.
|
||||||
|
self.stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: TextRoot> Drop for TextSpanIter<'a, R> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Return the internal stack.
|
||||||
|
let stack = core::mem::take(&mut self.stack);
|
||||||
|
self.scratch.recover(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System parameter for reading and writing text spans in a [`TextBlock`](crate::TextBlock).
|
||||||
|
///
|
||||||
|
/// `R` is the root text component, and `S` is the text span component on children.
|
||||||
|
#[derive(SystemParam)]
|
||||||
|
pub struct TextWriter<'w, 's, R: TextRoot> {
|
||||||
|
// This is a resource because two TextWriters can't run in parallel.
|
||||||
|
scratch: ResMut<'w, TextIterScratch>,
|
||||||
|
roots: Query<'w, 's, (&'static mut R, &'static mut TextStyle), Without<TextSpan>>,
|
||||||
|
spans: Query<'w, 's, (&'static mut TextSpan, &'static mut TextStyle), Without<R>>,
|
||||||
|
children: Query<'w, 's, &'static Children>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> {
|
||||||
|
/// Gets a mutable reference to a text span within a text block at a specific index in the flattened span list.
|
||||||
|
pub fn get(
|
||||||
|
&mut self,
|
||||||
|
root_entity: Entity,
|
||||||
|
index: usize,
|
||||||
|
) -> Option<(Entity, usize, Mut<String>, Mut<TextStyle>)> {
|
||||||
|
// Root
|
||||||
|
if index == 0 {
|
||||||
|
let (text, style) = self.roots.get_mut(root_entity).ok()?;
|
||||||
|
return Some((
|
||||||
|
root_entity,
|
||||||
|
0,
|
||||||
|
text.map_unchanged(|t| t.write_span()),
|
||||||
|
style,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prep stack.
|
||||||
|
let mut stack: Vec<(&Children, usize)> = self.scratch.take();
|
||||||
|
if let Ok(children) = self.children.get(root_entity) {
|
||||||
|
stack.push((children, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span
|
||||||
|
let mut count = 1;
|
||||||
|
let (depth, entity) = 'l: loop {
|
||||||
|
let Some((children, idx)) = stack.last_mut() else {
|
||||||
|
self.scratch.recover(stack);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(child) = children.get(*idx) else {
|
||||||
|
// All children at this stack entry have been iterated.
|
||||||
|
stack.pop();
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Increment to prep the next entity in this stack level.
|
||||||
|
*idx += 1;
|
||||||
|
|
||||||
|
if !self.spans.contains(*child) {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
if count - 1 == index {
|
||||||
|
let depth = stack.len();
|
||||||
|
self.scratch.recover(stack);
|
||||||
|
break 'l (depth, *child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(children) = self.children.get(*child) {
|
||||||
|
stack.push((children, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: We do this outside the loop due to borrow checker limitations.
|
||||||
|
let (text, style) = self.spans.get_mut(entity).unwrap();
|
||||||
|
Some((entity, depth, text.map_unchanged(|t| t.write_span()), style))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the text value of a text span within a text block at a specific index in the flattened span list.
|
||||||
|
pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option<Mut<String>> {
|
||||||
|
self.get(root_entity, index).map(|(_, _, text, _)| text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the [`TextStyle`] of a text span within a text block at a specific index in the flattened span list.
|
||||||
|
pub fn get_style(&mut self, root_entity: Entity, index: usize) -> Option<Mut<TextStyle>> {
|
||||||
|
self.get(root_entity, index).map(|(_, _, _, style)| style)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the text value of a text span within a text block at a specific index in the flattened span list.
|
||||||
|
///
|
||||||
|
/// Panics if there is no span at the requested index.
|
||||||
|
pub fn text(&mut self, root_entity: Entity, index: usize) -> Mut<String> {
|
||||||
|
self.get_text(root_entity, index).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the [`TextStyle`] of a text span within a text block at a specific index in the flattened span list.
|
||||||
|
///
|
||||||
|
/// Panics if there is no span at the requested index.
|
||||||
|
pub fn style(&mut self, root_entity: Entity, index: usize) -> Mut<TextStyle> {
|
||||||
|
self.get_style(root_entity, index).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invokes a callback on each span in a text block, starting with the root entity.
|
||||||
|
pub fn for_each(
|
||||||
|
&mut self,
|
||||||
|
root_entity: Entity,
|
||||||
|
mut callback: impl FnMut(Entity, usize, Mut<String>, Mut<TextStyle>),
|
||||||
|
) {
|
||||||
|
self.for_each_until(root_entity, |a, b, c, d| {
|
||||||
|
(callback)(a, b, c, d);
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invokes a callback on each span's string value in a text block, starting with the root entity.
|
||||||
|
pub fn for_each_text(&mut self, root_entity: Entity, mut callback: impl FnMut(Mut<String>)) {
|
||||||
|
self.for_each(root_entity, |_, _, text, _| {
|
||||||
|
(callback)(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invokes a callback on each span's [`TextStyle`] in a text block, starting with the root entity.
|
||||||
|
pub fn for_each_style(
|
||||||
|
&mut self,
|
||||||
|
root_entity: Entity,
|
||||||
|
mut callback: impl FnMut(Mut<TextStyle>),
|
||||||
|
) {
|
||||||
|
self.for_each(root_entity, |_, _, _, style| {
|
||||||
|
(callback)(style);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invokes a callback on each span in a text block, starting with the root entity.
|
||||||
|
///
|
||||||
|
/// Traversal will stop when the callback returns `false`.
|
||||||
|
// TODO: find a way to consolidate get and for_each_until, or provide a real iterator. Lifetime issues are challenging here.
|
||||||
|
pub fn for_each_until(
|
||||||
|
&mut self,
|
||||||
|
root_entity: Entity,
|
||||||
|
mut callback: impl FnMut(Entity, usize, Mut<String>, Mut<TextStyle>) -> bool,
|
||||||
|
) {
|
||||||
|
// Root
|
||||||
|
let Ok((text, style)) = self.roots.get_mut(root_entity) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !(callback)(
|
||||||
|
root_entity,
|
||||||
|
0,
|
||||||
|
text.map_unchanged(|t| t.write_span()),
|
||||||
|
style,
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prep stack.
|
||||||
|
let mut stack: Vec<(&Children, usize)> = self.scratch.take();
|
||||||
|
if let Ok(children) = self.children.get(root_entity) {
|
||||||
|
stack.push((children, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span
|
||||||
|
loop {
|
||||||
|
let depth = stack.len();
|
||||||
|
let Some((children, idx)) = stack.last_mut() else {
|
||||||
|
self.scratch.recover(stack);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(child) = children.get(*idx) else {
|
||||||
|
// All children at this stack entry have been iterated.
|
||||||
|
stack.pop();
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Increment to prep the next entity in this stack level.
|
||||||
|
*idx += 1;
|
||||||
|
|
||||||
|
let entity = *child;
|
||||||
|
let Ok((text, style)) = self.spans.get_mut(entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !(callback)(entity, depth, text.map_unchanged(|t| t.write_span()), style) {
|
||||||
|
self.scratch.recover(stack);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(children) = self.children.get(entity) {
|
||||||
|
stack.push((children, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::{Button, Label},
|
prelude::{Button, Label},
|
||||||
|
widget::UiTextReader,
|
||||||
Node, UiChildren, UiImage,
|
Node, UiChildren, UiImage,
|
||||||
};
|
};
|
||||||
use bevy_a11y::{
|
use bevy_a11y::{
|
||||||
|
@ -15,18 +16,19 @@ use bevy_ecs::{
|
||||||
world::Ref,
|
world::Ref,
|
||||||
};
|
};
|
||||||
use bevy_render::{camera::CameraUpdateSystem, prelude::Camera};
|
use bevy_render::{camera::CameraUpdateSystem, prelude::Camera};
|
||||||
use bevy_text::Text;
|
|
||||||
use bevy_transform::prelude::GlobalTransform;
|
use bevy_transform::prelude::GlobalTransform;
|
||||||
|
|
||||||
fn calc_name(texts: &Query<&Text>, children: impl Iterator<Item = Entity>) -> Option<Box<str>> {
|
fn calc_name(
|
||||||
|
text_reader: &mut UiTextReader,
|
||||||
|
children: impl Iterator<Item = Entity>,
|
||||||
|
) -> Option<Box<str>> {
|
||||||
let mut name = None;
|
let mut name = None;
|
||||||
for child in children {
|
for child in children {
|
||||||
if let Ok(text) = texts.get(child) {
|
let values = text_reader
|
||||||
let values = text
|
.iter(child)
|
||||||
.sections
|
.map(|(_, _, text, _)| text.into())
|
||||||
.iter()
|
.collect::<Vec<String>>();
|
||||||
.map(|v| v.value.to_string())
|
if !values.is_empty() {
|
||||||
.collect::<Vec<String>>();
|
|
||||||
name = Some(values.join(" "));
|
name = Some(values.join(" "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,10 +62,10 @@ fn button_changed(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<(Entity, Option<&mut AccessibilityNode>), Changed<Button>>,
|
mut query: Query<(Entity, Option<&mut AccessibilityNode>), Changed<Button>>,
|
||||||
ui_children: UiChildren,
|
ui_children: UiChildren,
|
||||||
texts: Query<&Text>,
|
mut text_reader: UiTextReader,
|
||||||
) {
|
) {
|
||||||
for (entity, accessible) in &mut query {
|
for (entity, accessible) in &mut query {
|
||||||
let name = calc_name(&texts, ui_children.iter_ui_children(entity));
|
let name = calc_name(&mut text_reader, ui_children.iter_ui_children(entity));
|
||||||
if let Some(mut accessible) = accessible {
|
if let Some(mut accessible) = accessible {
|
||||||
accessible.set_role(Role::Button);
|
accessible.set_role(Role::Button);
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
|
@ -87,10 +89,10 @@ fn image_changed(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<(Entity, Option<&mut AccessibilityNode>), (Changed<UiImage>, Without<Button>)>,
|
mut query: Query<(Entity, Option<&mut AccessibilityNode>), (Changed<UiImage>, Without<Button>)>,
|
||||||
ui_children: UiChildren,
|
ui_children: UiChildren,
|
||||||
texts: Query<&Text>,
|
mut text_reader: UiTextReader,
|
||||||
) {
|
) {
|
||||||
for (entity, accessible) in &mut query {
|
for (entity, accessible) in &mut query {
|
||||||
let name = calc_name(&texts, ui_children.iter_ui_children(entity));
|
let name = calc_name(&mut text_reader, ui_children.iter_ui_children(entity));
|
||||||
if let Some(mut accessible) = accessible {
|
if let Some(mut accessible) = accessible {
|
||||||
accessible.set_role(Role::Image);
|
accessible.set_role(Role::Image);
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
|
@ -112,13 +114,13 @@ fn image_changed(
|
||||||
|
|
||||||
fn label_changed(
|
fn label_changed(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<(Entity, &Text, Option<&mut AccessibilityNode>), Changed<Label>>,
|
mut query: Query<(Entity, Option<&mut AccessibilityNode>), Changed<Label>>,
|
||||||
|
mut text_reader: UiTextReader,
|
||||||
) {
|
) {
|
||||||
for (entity, text, accessible) in &mut query {
|
for (entity, accessible) in &mut query {
|
||||||
let values = text
|
let values = text_reader
|
||||||
.sections
|
.iter(entity)
|
||||||
.iter()
|
.map(|(_, _, text, _)| text.into())
|
||||||
.map(|v| v.value.to_string())
|
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
let name = Some(values.join(" ").into_boxed_str());
|
let name = Some(values.join(" ").into_boxed_str());
|
||||||
if let Some(mut accessible) = accessible {
|
if let Some(mut accessible) = accessible {
|
||||||
|
|
|
@ -22,7 +22,7 @@ use derive_more::derive::{Display, Error, From};
|
||||||
use ui_surface::UiSurface;
|
use ui_surface::UiSurface;
|
||||||
|
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
use bevy_text::CosmicBuffer;
|
use bevy_text::ComputedTextBlock;
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
use bevy_text::CosmicFontSystem;
|
use bevy_text::CosmicFontSystem;
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ pub fn ui_layout_system(
|
||||||
Option<&Outline>,
|
Option<&Outline>,
|
||||||
Option<&ScrollPosition>,
|
Option<&ScrollPosition>,
|
||||||
)>,
|
)>,
|
||||||
#[cfg(feature = "bevy_text")] mut buffer_query: Query<&mut CosmicBuffer>,
|
#[cfg(feature = "bevy_text")] mut buffer_query: Query<&mut ComputedTextBlock>,
|
||||||
#[cfg(feature = "bevy_text")] mut font_system: ResMut<CosmicFontSystem>,
|
#[cfg(feature = "bevy_text")] mut font_system: ResMut<CosmicFontSystem>,
|
||||||
) {
|
) {
|
||||||
let UiLayoutSystemBuffers {
|
let UiLayoutSystemBuffers {
|
||||||
|
|
|
@ -200,7 +200,7 @@ impl UiSurface {
|
||||||
camera: Entity,
|
camera: Entity,
|
||||||
render_target_resolution: UVec2,
|
render_target_resolution: UVec2,
|
||||||
#[cfg(feature = "bevy_text")] buffer_query: &'a mut bevy_ecs::prelude::Query<
|
#[cfg(feature = "bevy_text")] buffer_query: &'a mut bevy_ecs::prelude::Query<
|
||||||
&mut bevy_text::CosmicBuffer,
|
&mut bevy_text::ComputedTextBlock,
|
||||||
>,
|
>,
|
||||||
#[cfg(feature = "bevy_text")] font_system: &'a mut bevy_text::cosmic_text::FontSystem,
|
#[cfg(feature = "bevy_text")] font_system: &'a mut bevy_text::cosmic_text::FontSystem,
|
||||||
) {
|
) {
|
||||||
|
@ -302,8 +302,8 @@ with UI components as a child of an entity without UI components, your UI layout
|
||||||
fn get_text_buffer<'a>(
|
fn get_text_buffer<'a>(
|
||||||
needs_buffer: bool,
|
needs_buffer: bool,
|
||||||
ctx: &mut NodeMeasure,
|
ctx: &mut NodeMeasure,
|
||||||
query: &'a mut bevy_ecs::prelude::Query<&mut bevy_text::CosmicBuffer>,
|
query: &'a mut bevy_ecs::prelude::Query<&mut bevy_text::ComputedTextBlock>,
|
||||||
) -> Option<&'a mut bevy_text::cosmic_text::Buffer> {
|
) -> Option<&'a mut bevy_text::ComputedTextBlock> {
|
||||||
// We avoid a query lookup whenever the buffer is not required.
|
// We avoid a query lookup whenever the buffer is not required.
|
||||||
if !needs_buffer {
|
if !needs_buffer {
|
||||||
return None;
|
return None;
|
||||||
|
@ -311,8 +311,8 @@ fn get_text_buffer<'a>(
|
||||||
let NodeMeasure::Text(crate::widget::TextMeasure { info }) = ctx else {
|
let NodeMeasure::Text(crate::widget::TextMeasure { info }) = ctx else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let Ok(buffer) = query.get_mut(info.entity) else {
|
let Ok(computed) = query.get_mut(info.entity) else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
Some(buffer.into_inner())
|
Some(computed.into_inner())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
|
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
|
||||||
//! # Basic usage
|
//! # Basic usage
|
||||||
//! Spawn UI elements with [`node_bundles::ButtonBundle`], [`node_bundles::ImageBundle`], [`node_bundles::TextBundle`] and [`node_bundles::NodeBundle`]
|
//! Spawn UI elements with [`node_bundles::ButtonBundle`], [`node_bundles::ImageBundle`], [`Text`](prelude::Text) and [`node_bundles::NodeBundle`]
|
||||||
//! This UI is laid out with the Flexbox and CSS Grid layout models (see <https://cssreference.io/flexbox/>)
|
//! This UI is laid out with the Flexbox and CSS Grid layout models (see <https://cssreference.io/flexbox/>)
|
||||||
|
|
||||||
pub mod measurement;
|
pub mod measurement;
|
||||||
|
@ -49,8 +49,12 @@ pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use {
|
pub use {
|
||||||
crate::{
|
crate::{
|
||||||
geometry::*, node_bundles::*, ui_material::*, ui_node::*, widget::Button,
|
geometry::*,
|
||||||
widget::Label, Interaction, UiMaterialHandle, UiMaterialPlugin, UiScale,
|
node_bundles::*,
|
||||||
|
ui_material::*,
|
||||||
|
ui_node::*,
|
||||||
|
widget::{Button, Label, Text, UiTextReader, UiTextWriter},
|
||||||
|
Interaction, UiMaterialHandle, UiMaterialPlugin, UiScale,
|
||||||
},
|
},
|
||||||
// `bevy_sprite` re-exports for texture slicing
|
// `bevy_sprite` re-exports for texture slicing
|
||||||
bevy_sprite::{BorderRect, ImageScaleMode, SliceScaleMode, TextureSlicer},
|
bevy_sprite::{BorderRect, ImageScaleMode, SliceScaleMode, TextureSlicer},
|
||||||
|
@ -177,6 +181,7 @@ impl Plugin for UiPlugin {
|
||||||
.in_set(UiSystem::Layout)
|
.in_set(UiSystem::Layout)
|
||||||
.before(TransformSystem::TransformPropagate)
|
.before(TransformSystem::TransformPropagate)
|
||||||
// Text and Text2D operate on disjoint sets of entities
|
// Text and Text2D operate on disjoint sets of entities
|
||||||
|
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>)
|
||||||
.ambiguous_with(bevy_text::update_text2d_layout),
|
.ambiguous_with(bevy_text::update_text2d_layout),
|
||||||
ui_stack_system
|
ui_stack_system
|
||||||
.in_set(UiSystem::Stack)
|
.in_set(UiSystem::Stack)
|
||||||
|
@ -217,17 +222,25 @@ impl Plugin for UiPlugin {
|
||||||
/// A function that should be called from [`UiPlugin::build`] when [`bevy_text`] is enabled.
|
/// A function that should be called from [`UiPlugin::build`] when [`bevy_text`] is enabled.
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
fn build_text_interop(app: &mut App) {
|
fn build_text_interop(app: &mut App) {
|
||||||
use crate::widget::TextFlags;
|
use crate::widget::TextNodeFlags;
|
||||||
use bevy_text::TextLayoutInfo;
|
use bevy_text::TextLayoutInfo;
|
||||||
|
use widget::Text;
|
||||||
|
|
||||||
app.register_type::<TextLayoutInfo>()
|
app.register_type::<TextLayoutInfo>()
|
||||||
.register_type::<TextFlags>();
|
.register_type::<TextNodeFlags>()
|
||||||
|
.register_type::<Text>();
|
||||||
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
widget::measure_text_system
|
(
|
||||||
|
bevy_text::detect_text_needs_rerender::<Text>,
|
||||||
|
widget::measure_text_system,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
.in_set(UiSystem::Prepare)
|
.in_set(UiSystem::Prepare)
|
||||||
|
// Text and Text2d are independent.
|
||||||
|
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>)
|
||||||
// Potential conflict: `Assets<Image>`
|
// Potential conflict: `Assets<Image>`
|
||||||
// Since both systems will only ever insert new [`Image`] assets,
|
// Since both systems will only ever insert new [`Image`] assets,
|
||||||
// they will never observe each other's effects.
|
// they will never observe each other's effects.
|
||||||
|
@ -239,6 +252,7 @@ fn build_text_interop(app: &mut App) {
|
||||||
.in_set(UiSystem::PostLayout)
|
.in_set(UiSystem::PostLayout)
|
||||||
.after(bevy_text::remove_dropped_font_atlas_sets)
|
.after(bevy_text::remove_dropped_font_atlas_sets)
|
||||||
// Text2d and bevy_ui text are entirely on separate entities
|
// Text2d and bevy_ui text are entirely on separate entities
|
||||||
|
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>)
|
||||||
.ambiguous_with(bevy_text::update_text2d_layout)
|
.ambiguous_with(bevy_text::update_text2d_layout)
|
||||||
.ambiguous_with(bevy_text::calculate_bounds_text2d),
|
.ambiguous_with(bevy_text::calculate_bounds_text2d),
|
||||||
),
|
),
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub struct MeasureArgs<'a> {
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
pub font_system: &'a mut bevy_text::cosmic_text::FontSystem,
|
pub font_system: &'a mut bevy_text::cosmic_text::FontSystem,
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
pub buffer: Option<&'a mut bevy_text::cosmic_text::Buffer>,
|
pub buffer: Option<&'a mut bevy_text::ComputedTextBlock>,
|
||||||
// When `bevy_text` is disabled, use `PhantomData` in order to keep lifetime in type signature.
|
// When `bevy_text` is disabled, use `PhantomData` in order to keep lifetime in type signature.
|
||||||
#[cfg(not(feature = "bevy_text"))]
|
#[cfg(not(feature = "bevy_text"))]
|
||||||
pub font_system: core::marker::PhantomData<&'a mut ()>,
|
pub font_system: core::marker::PhantomData<&'a mut ()>,
|
||||||
|
|
|
@ -9,20 +9,11 @@ use bevy_ecs::bundle::Bundle;
|
||||||
use bevy_render::view::{InheritedVisibility, ViewVisibility, Visibility};
|
use bevy_render::view::{InheritedVisibility, ViewVisibility, Visibility};
|
||||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||||
|
|
||||||
#[cfg(feature = "bevy_text")]
|
|
||||||
use {
|
|
||||||
crate::widget::TextFlags,
|
|
||||||
bevy_color::Color,
|
|
||||||
bevy_text::{
|
|
||||||
CosmicBuffer, JustifyText, LineBreak, Text, TextLayoutInfo, TextSection, TextStyle,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The basic UI node.
|
/// The basic UI node.
|
||||||
///
|
///
|
||||||
/// Contains the [`Node`] component and other components required to make a container.
|
/// Contains the [`Node`] component and other components required to make a container.
|
||||||
///
|
///
|
||||||
/// See [`node_bundles`](crate::node_bundles) for more specialized bundles like [`TextBundle`].
|
/// See [`node_bundles`](crate::node_bundles) for more specialized bundles like [`ImageBundle`].
|
||||||
#[derive(Bundle, Clone, Debug, Default)]
|
#[derive(Bundle, Clone, Debug, Default)]
|
||||||
pub struct NodeBundle {
|
pub struct NodeBundle {
|
||||||
/// Describes the logical size of the node
|
/// Describes the logical size of the node
|
||||||
|
@ -109,109 +100,6 @@ pub struct ImageBundle {
|
||||||
pub z_index: ZIndex,
|
pub z_index: ZIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bevy_text")]
|
|
||||||
/// A UI node that is text
|
|
||||||
///
|
|
||||||
/// The positioning of this node is controlled by the UI layout system. If you need manual control,
|
|
||||||
/// use [`Text2dBundle`](bevy_text::Text2dBundle).
|
|
||||||
#[derive(Bundle, Debug, Default)]
|
|
||||||
pub struct TextBundle {
|
|
||||||
/// Describes the logical size of the node
|
|
||||||
pub node: Node,
|
|
||||||
/// Styles which control the layout (size and position) of the node and its children
|
|
||||||
/// In some cases these styles also affect how the node drawn/painted.
|
|
||||||
pub style: Style,
|
|
||||||
/// Contains the text of the node
|
|
||||||
pub text: Text,
|
|
||||||
/// Cached cosmic buffer for layout
|
|
||||||
pub buffer: CosmicBuffer,
|
|
||||||
/// Text layout information
|
|
||||||
pub text_layout_info: TextLayoutInfo,
|
|
||||||
/// Text system flags
|
|
||||||
pub text_flags: TextFlags,
|
|
||||||
/// The calculated size based on the given image
|
|
||||||
pub calculated_size: ContentSize,
|
|
||||||
/// Whether this node should block interaction with lower nodes
|
|
||||||
pub focus_policy: FocusPolicy,
|
|
||||||
/// The transform of the node
|
|
||||||
///
|
|
||||||
/// This component is automatically managed by the UI layout system.
|
|
||||||
/// To alter the position of the `TextBundle`, use the properties of the [`Style`] component.
|
|
||||||
pub transform: Transform,
|
|
||||||
/// The global transform of the node
|
|
||||||
///
|
|
||||||
/// This component is automatically updated by the [`TransformPropagate`](`bevy_transform::TransformSystem::TransformPropagate`) systems.
|
|
||||||
pub global_transform: GlobalTransform,
|
|
||||||
/// Describes the visibility properties of the node
|
|
||||||
pub visibility: Visibility,
|
|
||||||
/// Inherited visibility of an entity.
|
|
||||||
pub inherited_visibility: InheritedVisibility,
|
|
||||||
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
|
||||||
pub view_visibility: ViewVisibility,
|
|
||||||
/// Indicates the depth at which the node should appear in the UI
|
|
||||||
pub z_index: ZIndex,
|
|
||||||
/// The background color that will fill the containing node
|
|
||||||
pub background_color: BackgroundColor,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_text")]
|
|
||||||
impl TextBundle {
|
|
||||||
/// Create a [`TextBundle`] from a single section.
|
|
||||||
///
|
|
||||||
/// See [`Text::from_section`] for usage.
|
|
||||||
pub fn from_section(value: impl Into<String>, style: TextStyle) -> Self {
|
|
||||||
Self {
|
|
||||||
text: Text::from_section(value, style),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a [`TextBundle`] from a list of sections.
|
|
||||||
///
|
|
||||||
/// See [`Text::from_sections`] for usage.
|
|
||||||
pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
|
|
||||||
Self {
|
|
||||||
text: Text::from_sections(sections),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns this [`TextBundle`] with a new [`JustifyText`] on [`Text`].
|
|
||||||
pub const fn with_text_justify(mut self, justify: JustifyText) -> Self {
|
|
||||||
self.text.justify = justify;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns this [`TextBundle`] with a new [`Style`].
|
|
||||||
pub fn with_style(mut self, style: Style) -> Self {
|
|
||||||
self.style = style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns this [`TextBundle`] with a new [`BackgroundColor`].
|
|
||||||
pub const fn with_background_color(mut self, color: Color) -> Self {
|
|
||||||
self.background_color = BackgroundColor(color);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns this [`TextBundle`] with soft wrapping disabled.
|
|
||||||
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
|
|
||||||
pub const fn with_no_wrap(mut self) -> Self {
|
|
||||||
self.text.linebreak = LineBreak::NoWrap;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_text")]
|
|
||||||
impl<I> From<I> for TextBundle
|
|
||||||
where
|
|
||||||
I: Into<TextSection>,
|
|
||||||
{
|
|
||||||
fn from(value: I) -> Self {
|
|
||||||
Self::from_sections(vec![value.into()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A UI node that is a button
|
/// A UI node that is a button
|
||||||
///
|
///
|
||||||
/// # Extra behaviours
|
/// # Extra behaviours
|
||||||
|
|
|
@ -40,11 +40,7 @@ use bevy_render::{
|
||||||
use bevy_sprite::TextureAtlasLayout;
|
use bevy_sprite::TextureAtlasLayout;
|
||||||
use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents, TextureAtlas};
|
use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents, TextureAtlas};
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
use bevy_text::PositionedGlyph;
|
use bevy_text::{ComputedTextBlock, PositionedGlyph, TextLayoutInfo, TextStyle};
|
||||||
#[cfg(feature = "bevy_text")]
|
|
||||||
use bevy_text::Text;
|
|
||||||
#[cfg(feature = "bevy_text")]
|
|
||||||
use bevy_text::TextLayoutInfo;
|
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use box_shadow::BoxShadowPlugin;
|
use box_shadow::BoxShadowPlugin;
|
||||||
|
@ -593,18 +589,26 @@ pub fn extract_text_sections(
|
||||||
&ViewVisibility,
|
&ViewVisibility,
|
||||||
Option<&CalculatedClip>,
|
Option<&CalculatedClip>,
|
||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
&Text,
|
&ComputedTextBlock,
|
||||||
&TextLayoutInfo,
|
&TextLayoutInfo,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
|
text_styles: Extract<Query<&TextStyle>>,
|
||||||
mapping: Extract<Query<&RenderEntity>>,
|
mapping: Extract<Query<&RenderEntity>>,
|
||||||
) {
|
) {
|
||||||
let mut start = 0;
|
let mut start = 0;
|
||||||
let mut end = 1;
|
let mut end = 1;
|
||||||
|
|
||||||
let default_ui_camera = default_ui_camera.get();
|
let default_ui_camera = default_ui_camera.get();
|
||||||
for (uinode, global_transform, view_visibility, clip, camera, text, text_layout_info) in
|
for (
|
||||||
&uinode_query
|
uinode,
|
||||||
|
global_transform,
|
||||||
|
view_visibility,
|
||||||
|
clip,
|
||||||
|
camera,
|
||||||
|
computed_block,
|
||||||
|
text_layout_info,
|
||||||
|
) in &uinode_query
|
||||||
{
|
{
|
||||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera) else {
|
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera) else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -642,16 +646,31 @@ pub fn extract_text_sections(
|
||||||
transform.translation = transform.translation.round();
|
transform.translation = transform.translation.round();
|
||||||
transform.translation *= inverse_scale_factor;
|
transform.translation *= inverse_scale_factor;
|
||||||
|
|
||||||
|
let mut color = LinearRgba::WHITE;
|
||||||
|
let mut current_span = usize::MAX;
|
||||||
for (
|
for (
|
||||||
i,
|
i,
|
||||||
PositionedGlyph {
|
PositionedGlyph {
|
||||||
position,
|
position,
|
||||||
atlas_info,
|
atlas_info,
|
||||||
section_index,
|
span_index,
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
) in text_layout_info.glyphs.iter().enumerate()
|
) in text_layout_info.glyphs.iter().enumerate()
|
||||||
{
|
{
|
||||||
|
if *span_index != current_span {
|
||||||
|
color = text_styles
|
||||||
|
.get(
|
||||||
|
computed_block
|
||||||
|
.entities()
|
||||||
|
.get(*span_index)
|
||||||
|
.map(|t| t.entity)
|
||||||
|
.unwrap_or(Entity::PLACEHOLDER),
|
||||||
|
)
|
||||||
|
.map(|style| LinearRgba::from(style.color))
|
||||||
|
.unwrap_or_default();
|
||||||
|
current_span = *span_index;
|
||||||
|
}
|
||||||
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
||||||
|
|
||||||
let mut rect = atlas.textures[atlas_info.location.glyph_index].as_rect();
|
let mut rect = atlas.textures[atlas_info.location.glyph_index].as_rect();
|
||||||
|
@ -668,8 +687,7 @@ pub fn extract_text_sections(
|
||||||
.glyphs
|
.glyphs
|
||||||
.get(i + 1)
|
.get(i + 1)
|
||||||
.map(|info| {
|
.map(|info| {
|
||||||
info.section_index != *section_index
|
info.span_index != current_span || info.atlas_info.texture != atlas_info.texture
|
||||||
|| info.atlas_info.texture != atlas_info.texture
|
|
||||||
})
|
})
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
|
@ -679,7 +697,7 @@ pub fn extract_text_sections(
|
||||||
id,
|
id,
|
||||||
ExtractedUiNode {
|
ExtractedUiNode {
|
||||||
stack_index: uinode.stack_index,
|
stack_index: uinode.stack_index,
|
||||||
color: LinearRgba::from(text.sections[*section_index].style.color),
|
color,
|
||||||
image: atlas_info.texture.id(),
|
image: atlas_info.texture.id(),
|
||||||
clip: clip.map(|clip| clip.clip),
|
clip: clip.map(|clip| clip.clip),
|
||||||
camera_entity: render_camera_entity.id(),
|
camera_entity: render_camera_entity.id(),
|
||||||
|
|
|
@ -2520,7 +2520,7 @@ impl<'w, 's> DefaultUiCamera<'w, 's> {
|
||||||
/// Marker for controlling whether Ui is rendered with or without anti-aliasing
|
/// Marker for controlling whether Ui is rendered with or without anti-aliasing
|
||||||
/// in a camera. By default, Ui is always anti-aliased.
|
/// in a camera. By default, Ui is always anti-aliased.
|
||||||
///
|
///
|
||||||
/// **Note:** This does not affect text anti-aliasing. For that, use the `font_smoothing` property of the [`bevy_text::Text`] component.
|
/// **Note:** This does not affect text anti-aliasing. For that, use the `font_smoothing` property of the [`TextStyle`](bevy_text::TextStyle) component.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use bevy_core_pipeline::prelude::*;
|
/// use bevy_core_pipeline::prelude::*;
|
||||||
|
|
|
@ -88,7 +88,7 @@ impl Measure for ImageMeasure {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
type UpdateImageFilter = (With<Node>, Without<bevy_text::Text>);
|
type UpdateImageFilter = (With<Node>, Without<crate::prelude::Text>);
|
||||||
#[cfg(not(feature = "bevy_text"))]
|
#[cfg(not(feature = "bevy_text"))]
|
||||||
type UpdateImageFilter = With<Node>;
|
type UpdateImageFilter = With<Node>;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ContentSize, DefaultUiCamera, FixedMeasure, Measure, MeasureArgs, Node, NodeMeasure,
|
ContentSize, DefaultUiCamera, FixedMeasure, FocusPolicy, Measure, MeasureArgs, Node,
|
||||||
TargetCamera, UiScale,
|
NodeMeasure, Style, TargetCamera, UiScale, ZIndex,
|
||||||
};
|
};
|
||||||
use bevy_asset::Assets;
|
use bevy_asset::Assets;
|
||||||
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
change_detection::DetectChanges,
|
||||||
entity::{Entity, EntityHashMap},
|
entity::{Entity, EntityHashMap},
|
||||||
prelude::{Component, DetectChanges},
|
prelude::Component,
|
||||||
query::With,
|
query::With,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
system::{Local, Query, Res, ResMut},
|
system::{Local, Query, Res, ResMut},
|
||||||
|
@ -13,37 +15,132 @@ use bevy_ecs::{
|
||||||
};
|
};
|
||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::{camera::Camera, texture::Image};
|
use bevy_render::{camera::Camera, texture::Image, view::Visibility};
|
||||||
use bevy_sprite::TextureAtlasLayout;
|
use bevy_sprite::TextureAtlasLayout;
|
||||||
use bevy_text::{
|
use bevy_text::{
|
||||||
scale_value, CosmicBuffer, CosmicFontSystem, Font, FontAtlasSets, JustifyText, LineBreak,
|
scale_value, ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSets, LineBreak, SwashCache,
|
||||||
SwashCache, Text, TextBounds, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline,
|
TextBlock, TextBounds, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextReader,
|
||||||
YAxisOrientation,
|
TextRoot, TextSpanAccess, TextStyle, TextWriter, YAxisOrientation,
|
||||||
};
|
};
|
||||||
|
use bevy_transform::components::Transform;
|
||||||
use bevy_utils::{tracing::error, Entry};
|
use bevy_utils::{tracing::error, Entry};
|
||||||
use taffy::style::AvailableSpace;
|
use taffy::style::AvailableSpace;
|
||||||
|
|
||||||
/// Text system flags
|
/// UI text system flags.
|
||||||
///
|
///
|
||||||
/// Used internally by [`measure_text_system`] and [`text_system`] to schedule text for processing.
|
/// Used internally by [`measure_text_system`] and [`text_system`] to schedule text for processing.
|
||||||
#[derive(Component, Debug, Clone, Reflect)]
|
#[derive(Component, Debug, Clone, Reflect)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct TextFlags {
|
pub struct TextNodeFlags {
|
||||||
/// If set a new measure function for the text node will be created
|
/// If set then a new measure function for the text node will be created.
|
||||||
needs_new_measure_func: bool,
|
needs_measure_fn: bool,
|
||||||
/// If set the text will be recomputed
|
/// If set then the text will be recomputed.
|
||||||
needs_recompute: bool,
|
needs_recompute: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextFlags {
|
impl Default for TextNodeFlags {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
needs_new_measure_func: true,
|
needs_measure_fn: true,
|
||||||
needs_recompute: true,
|
needs_recompute: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The top-level UI text component.
|
||||||
|
///
|
||||||
|
/// Adding [`Text`] to an entity will pull in required components for setting up a UI text node.
|
||||||
|
///
|
||||||
|
/// The string in this component is the first 'text span' in a hierarchy of text spans that are collected into
|
||||||
|
/// a [`TextBlock`]. See [`TextSpan`](bevy_text::TextSpan) for the component used by children of entities with [`Text`].
|
||||||
|
///
|
||||||
|
/// Note that [`Transform`] on this entity is managed automatically by the UI layout system.
|
||||||
|
///
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
# use bevy_asset::Handle;
|
||||||
|
# use bevy_color::Color;
|
||||||
|
# use bevy_color::palettes::basic::BLUE;
|
||||||
|
# use bevy_ecs::World;
|
||||||
|
# use bevy_text::{Font, JustifyText, TextBlock, TextStyle};
|
||||||
|
# use bevy_ui::Text;
|
||||||
|
#
|
||||||
|
# let font_handle: Handle<Font> = Default::default();
|
||||||
|
# let mut world = World::default();
|
||||||
|
#
|
||||||
|
// Basic usage.
|
||||||
|
world.spawn(Text::new("hello world!"));
|
||||||
|
|
||||||
|
// With non-default style.
|
||||||
|
world.spawn((
|
||||||
|
Text::new("hello world!"),
|
||||||
|
TextStyle {
|
||||||
|
font: font_handle.clone().into(),
|
||||||
|
font_size: 60.0,
|
||||||
|
color: BLUE.into(),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// With text justification.
|
||||||
|
world.spawn((
|
||||||
|
Text::new("hello world\nand bevy!"),
|
||||||
|
TextBlock::new_with_justify(JustifyText::Center)
|
||||||
|
));
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
#[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)]
|
||||||
|
#[reflect(Component, Default, Debug)]
|
||||||
|
#[require(
|
||||||
|
TextBlock,
|
||||||
|
TextStyle,
|
||||||
|
TextNodeFlags,
|
||||||
|
Node,
|
||||||
|
Style, // TODO: Remove when Node uses required components.
|
||||||
|
ContentSize, // TODO: Remove when Node uses required components.
|
||||||
|
FocusPolicy, // TODO: Remove when Node uses required components.
|
||||||
|
ZIndex, // TODO: Remove when Node uses required components.
|
||||||
|
Visibility, // TODO: Remove when Node uses required components.
|
||||||
|
Transform // TODO: Remove when Node uses required components.
|
||||||
|
)]
|
||||||
|
pub struct Text(pub String);
|
||||||
|
|
||||||
|
impl Text {
|
||||||
|
/// Makes a new text component.
|
||||||
|
pub fn new(text: impl Into<String>) -> Self {
|
||||||
|
Self(text.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextRoot for Text {}
|
||||||
|
|
||||||
|
impl TextSpanAccess for Text {
|
||||||
|
fn read_span(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
fn write_span(&mut self) -> &mut String {
|
||||||
|
&mut *self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Text {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self(String::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Text {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UI alias for [`TextReader`].
|
||||||
|
pub type UiTextReader<'w, 's> = TextReader<'w, 's, Text>;
|
||||||
|
|
||||||
|
/// UI alias for [`TextWriter`].
|
||||||
|
pub type UiTextWriter<'w, 's> = TextWriter<'w, 's, Text>;
|
||||||
|
|
||||||
|
/// Text measurement for UI layout. See [`NodeMeasure`].
|
||||||
pub struct TextMeasure {
|
pub struct TextMeasure {
|
||||||
pub info: TextMeasureInfo,
|
pub info: TextMeasureInfo,
|
||||||
}
|
}
|
||||||
|
@ -103,42 +200,41 @@ impl Measure for TextMeasure {
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn create_text_measure(
|
fn create_text_measure<'a>(
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
fonts: &Assets<Font>,
|
fonts: &Assets<Font>,
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
text: Ref<Text>,
|
spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>,
|
||||||
|
block: Ref<TextBlock>,
|
||||||
text_pipeline: &mut TextPipeline,
|
text_pipeline: &mut TextPipeline,
|
||||||
mut content_size: Mut<ContentSize>,
|
mut content_size: Mut<ContentSize>,
|
||||||
mut text_flags: Mut<TextFlags>,
|
mut text_flags: Mut<TextNodeFlags>,
|
||||||
buffer: &mut CosmicBuffer,
|
mut computed: Mut<ComputedTextBlock>,
|
||||||
text_alignment: JustifyText,
|
|
||||||
font_system: &mut CosmicFontSystem,
|
font_system: &mut CosmicFontSystem,
|
||||||
) {
|
) {
|
||||||
match text_pipeline.create_text_measure(
|
match text_pipeline.create_text_measure(
|
||||||
entity,
|
entity,
|
||||||
fonts,
|
fonts,
|
||||||
&text.sections,
|
spans,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
text.linebreak,
|
&block,
|
||||||
buffer,
|
computed.as_mut(),
|
||||||
text_alignment,
|
|
||||||
font_system,
|
font_system,
|
||||||
) {
|
) {
|
||||||
Ok(measure) => {
|
Ok(measure) => {
|
||||||
if text.linebreak == LineBreak::NoWrap {
|
if block.linebreak == LineBreak::NoWrap {
|
||||||
content_size.set(NodeMeasure::Fixed(FixedMeasure { size: measure.max }));
|
content_size.set(NodeMeasure::Fixed(FixedMeasure { size: measure.max }));
|
||||||
} else {
|
} else {
|
||||||
content_size.set(NodeMeasure::Text(TextMeasure { info: measure }));
|
content_size.set(NodeMeasure::Text(TextMeasure { info: measure }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text measure func created successfully, so set `TextFlags` to schedule a recompute
|
// Text measure func created successfully, so set `TextNodeFlags` to schedule a recompute
|
||||||
text_flags.needs_new_measure_func = false;
|
text_flags.needs_measure_fn = false;
|
||||||
text_flags.needs_recompute = true;
|
text_flags.needs_recompute = true;
|
||||||
}
|
}
|
||||||
Err(TextError::NoSuchFont) => {
|
Err(TextError::NoSuchFont) => {
|
||||||
// Try again next frame
|
// Try again next frame
|
||||||
text_flags.needs_new_measure_func = true;
|
text_flags.needs_measure_fn = true;
|
||||||
}
|
}
|
||||||
Err(e @ (TextError::FailedToAddGlyph(_) | TextError::FailedToGetGlyphImage(_))) => {
|
Err(e @ (TextError::FailedToAddGlyph(_) | TextError::FailedToGetGlyphImage(_))) => {
|
||||||
panic!("Fatal error when processing text: {e}.");
|
panic!("Fatal error when processing text: {e}.");
|
||||||
|
@ -167,21 +263,24 @@ pub fn measure_text_system(
|
||||||
mut text_query: Query<
|
mut text_query: Query<
|
||||||
(
|
(
|
||||||
Entity,
|
Entity,
|
||||||
Ref<Text>,
|
Ref<TextBlock>,
|
||||||
&mut ContentSize,
|
&mut ContentSize,
|
||||||
&mut TextFlags,
|
&mut TextNodeFlags,
|
||||||
|
&mut ComputedTextBlock,
|
||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
&mut CosmicBuffer,
|
|
||||||
),
|
),
|
||||||
With<Node>,
|
With<Node>,
|
||||||
>,
|
>,
|
||||||
|
mut text_reader: UiTextReader,
|
||||||
mut text_pipeline: ResMut<TextPipeline>,
|
mut text_pipeline: ResMut<TextPipeline>,
|
||||||
mut font_system: ResMut<CosmicFontSystem>,
|
mut font_system: ResMut<CosmicFontSystem>,
|
||||||
) {
|
) {
|
||||||
scale_factors_buffer.clear();
|
scale_factors_buffer.clear();
|
||||||
|
|
||||||
for (entity, text, content_size, text_flags, camera, mut buffer) in &mut text_query {
|
for (entity, block, content_size, text_flags, computed, maybe_camera) in &mut text_query {
|
||||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
let Some(camera_entity) = maybe_camera
|
||||||
|
.map(TargetCamera::entity)
|
||||||
|
.or(default_ui_camera.get())
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -196,22 +295,22 @@ pub fn measure_text_system(
|
||||||
* ui_scale.0,
|
* ui_scale.0,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
// Note: the ComputedTextBlock::needs_rerender bool is cleared in create_text_measure().
|
||||||
if last_scale_factors.get(&camera_entity) != Some(&scale_factor)
|
if last_scale_factors.get(&camera_entity) != Some(&scale_factor)
|
||||||
|| text.is_changed()
|
|| computed.needs_rerender()
|
||||||
|| text_flags.needs_new_measure_func
|
|| text_flags.needs_measure_fn
|
||||||
|| content_size.is_added()
|
|| content_size.is_added()
|
||||||
{
|
{
|
||||||
let text_alignment = text.justify;
|
|
||||||
create_text_measure(
|
create_text_measure(
|
||||||
entity,
|
entity,
|
||||||
&fonts,
|
&fonts,
|
||||||
scale_factor.into(),
|
scale_factor.into(),
|
||||||
text,
|
text_reader.iter(entity),
|
||||||
|
block,
|
||||||
&mut text_pipeline,
|
&mut text_pipeline,
|
||||||
content_size,
|
content_size,
|
||||||
text_flags,
|
text_flags,
|
||||||
buffer.as_mut(),
|
computed,
|
||||||
text_alignment,
|
|
||||||
&mut font_system,
|
&mut font_system,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -222,6 +321,7 @@ pub fn measure_text_system(
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn queue_text(
|
fn queue_text(
|
||||||
|
entity: Entity,
|
||||||
fonts: &Assets<Font>,
|
fonts: &Assets<Font>,
|
||||||
text_pipeline: &mut TextPipeline,
|
text_pipeline: &mut TextPipeline,
|
||||||
font_atlas_sets: &mut FontAtlasSets,
|
font_atlas_sets: &mut FontAtlasSets,
|
||||||
|
@ -229,65 +329,64 @@ fn queue_text(
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
inverse_scale_factor: f32,
|
inverse_scale_factor: f32,
|
||||||
text: &Text,
|
block: &TextBlock,
|
||||||
node: Ref<Node>,
|
node: Ref<Node>,
|
||||||
mut text_flags: Mut<TextFlags>,
|
mut text_flags: Mut<TextNodeFlags>,
|
||||||
text_layout_info: Mut<TextLayoutInfo>,
|
text_layout_info: Mut<TextLayoutInfo>,
|
||||||
buffer: &mut CosmicBuffer,
|
computed: &mut ComputedTextBlock,
|
||||||
|
text_reader: &mut UiTextReader,
|
||||||
font_system: &mut CosmicFontSystem,
|
font_system: &mut CosmicFontSystem,
|
||||||
swash_cache: &mut SwashCache,
|
swash_cache: &mut SwashCache,
|
||||||
) {
|
) {
|
||||||
// Skip the text node if it is waiting for a new measure func
|
// Skip the text node if it is waiting for a new measure func
|
||||||
if !text_flags.needs_new_measure_func {
|
if text_flags.needs_measure_fn {
|
||||||
let physical_node_size = if text.linebreak == LineBreak::NoWrap {
|
return;
|
||||||
// With `NoWrap` set, no constraints are placed on the width of the text.
|
}
|
||||||
TextBounds::UNBOUNDED
|
|
||||||
} else {
|
|
||||||
// `scale_factor` is already multiplied by `UiScale`
|
|
||||||
TextBounds::new(
|
|
||||||
node.unrounded_size.x * scale_factor,
|
|
||||||
node.unrounded_size.y * scale_factor,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let text_layout_info = text_layout_info.into_inner();
|
let physical_node_size = if block.linebreak == LineBreak::NoWrap {
|
||||||
match text_pipeline.queue_text(
|
// With `NoWrap` set, no constraints are placed on the width of the text.
|
||||||
text_layout_info,
|
TextBounds::UNBOUNDED
|
||||||
fonts,
|
} else {
|
||||||
&text.sections,
|
// `scale_factor` is already multiplied by `UiScale`
|
||||||
scale_factor.into(),
|
TextBounds::new(
|
||||||
text.justify,
|
node.unrounded_size.x * scale_factor,
|
||||||
text.linebreak,
|
node.unrounded_size.y * scale_factor,
|
||||||
text.font_smoothing,
|
)
|
||||||
physical_node_size,
|
};
|
||||||
font_atlas_sets,
|
|
||||||
texture_atlases,
|
let text_layout_info = text_layout_info.into_inner();
|
||||||
textures,
|
match text_pipeline.queue_text(
|
||||||
YAxisOrientation::TopToBottom,
|
text_layout_info,
|
||||||
buffer,
|
fonts,
|
||||||
font_system,
|
text_reader.iter(entity),
|
||||||
swash_cache,
|
scale_factor.into(),
|
||||||
) {
|
block,
|
||||||
Err(TextError::NoSuchFont) => {
|
physical_node_size,
|
||||||
// There was an error processing the text layout, try again next frame
|
font_atlas_sets,
|
||||||
text_flags.needs_recompute = true;
|
texture_atlases,
|
||||||
}
|
textures,
|
||||||
Err(e @ (TextError::FailedToAddGlyph(_) | TextError::FailedToGetGlyphImage(_))) => {
|
YAxisOrientation::TopToBottom,
|
||||||
panic!("Fatal error when processing text: {e}.");
|
computed,
|
||||||
}
|
font_system,
|
||||||
Ok(()) => {
|
swash_cache,
|
||||||
text_layout_info.size.x =
|
) {
|
||||||
scale_value(text_layout_info.size.x, inverse_scale_factor);
|
Err(TextError::NoSuchFont) => {
|
||||||
text_layout_info.size.y =
|
// There was an error processing the text layout, try again next frame
|
||||||
scale_value(text_layout_info.size.y, inverse_scale_factor);
|
text_flags.needs_recompute = true;
|
||||||
text_flags.needs_recompute = false;
|
}
|
||||||
}
|
Err(e @ (TextError::FailedToAddGlyph(_) | TextError::FailedToGetGlyphImage(_))) => {
|
||||||
|
panic!("Fatal error when processing text: {e}.");
|
||||||
|
}
|
||||||
|
Ok(()) => {
|
||||||
|
text_layout_info.size.x = scale_value(text_layout_info.size.x, inverse_scale_factor);
|
||||||
|
text_layout_info.size.y = scale_value(text_layout_info.size.y, inverse_scale_factor);
|
||||||
|
text_flags.needs_recompute = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the layout and size information for a UI text node on changes to the size value of its [`Node`] component,
|
/// Updates the layout and size information for a UI text node on changes to the size value of its [`Node`] component,
|
||||||
/// or when the `needs_recompute` field of [`TextFlags`] is set to true.
|
/// or when the `needs_recompute` field of [`TextNodeFlags`] is set to true.
|
||||||
/// This information is computed by the [`TextPipeline`] and then stored in [`TextLayoutInfo`].
|
/// This information is computed by the [`TextPipeline`] and then stored in [`TextLayoutInfo`].
|
||||||
///
|
///
|
||||||
/// ## World Resources
|
/// ## World Resources
|
||||||
|
@ -307,20 +406,26 @@ pub fn text_system(
|
||||||
mut font_atlas_sets: ResMut<FontAtlasSets>,
|
mut font_atlas_sets: ResMut<FontAtlasSets>,
|
||||||
mut text_pipeline: ResMut<TextPipeline>,
|
mut text_pipeline: ResMut<TextPipeline>,
|
||||||
mut text_query: Query<(
|
mut text_query: Query<(
|
||||||
|
Entity,
|
||||||
Ref<Node>,
|
Ref<Node>,
|
||||||
&Text,
|
&TextBlock,
|
||||||
&mut TextLayoutInfo,
|
&mut TextLayoutInfo,
|
||||||
&mut TextFlags,
|
&mut TextNodeFlags,
|
||||||
|
&mut ComputedTextBlock,
|
||||||
Option<&TargetCamera>,
|
Option<&TargetCamera>,
|
||||||
&mut CosmicBuffer,
|
|
||||||
)>,
|
)>,
|
||||||
|
mut text_reader: UiTextReader,
|
||||||
mut font_system: ResMut<CosmicFontSystem>,
|
mut font_system: ResMut<CosmicFontSystem>,
|
||||||
mut swash_cache: ResMut<SwashCache>,
|
mut swash_cache: ResMut<SwashCache>,
|
||||||
) {
|
) {
|
||||||
scale_factors_buffer.clear();
|
scale_factors_buffer.clear();
|
||||||
|
|
||||||
for (node, text, text_layout_info, text_flags, camera, mut buffer) in &mut text_query {
|
for (entity, node, block, text_layout_info, text_flags, mut computed, maybe_camera) in
|
||||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
&mut text_query
|
||||||
|
{
|
||||||
|
let Some(camera_entity) = maybe_camera
|
||||||
|
.map(TargetCamera::entity)
|
||||||
|
.or(default_ui_camera.get())
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -342,6 +447,7 @@ pub fn text_system(
|
||||||
|| text_flags.needs_recompute
|
|| text_flags.needs_recompute
|
||||||
{
|
{
|
||||||
queue_text(
|
queue_text(
|
||||||
|
entity,
|
||||||
&fonts,
|
&fonts,
|
||||||
&mut text_pipeline,
|
&mut text_pipeline,
|
||||||
&mut font_atlas_sets,
|
&mut font_atlas_sets,
|
||||||
|
@ -349,11 +455,12 @@ pub fn text_system(
|
||||||
&mut textures,
|
&mut textures,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
inverse_scale_factor,
|
inverse_scale_factor,
|
||||||
text,
|
block,
|
||||||
node,
|
node,
|
||||||
text_flags,
|
text_flags,
|
||||||
text_layout_info,
|
text_layout_info,
|
||||||
buffer.as_mut(),
|
computed.as_mut(),
|
||||||
|
&mut text_reader,
|
||||||
&mut font_system,
|
&mut font_system,
|
||||||
&mut swash_cache,
|
&mut swash_cache,
|
||||||
);
|
);
|
||||||
|
|
|
@ -64,15 +64,15 @@ fn setup(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("Press space to toggle wireframes", TextStyle::default())
|
Text::new("Press space to toggle wireframes"),
|
||||||
.with_style(Style {
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
|
@ -57,14 +57,15 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
@ -78,11 +79,10 @@ fn update_bloom_settings(
|
||||||
) {
|
) {
|
||||||
let bloom = camera.single_mut();
|
let bloom = camera.single_mut();
|
||||||
let mut text = text.single_mut();
|
let mut text = text.single_mut();
|
||||||
let text = &mut text.sections[0].value;
|
|
||||||
|
|
||||||
match bloom {
|
match bloom {
|
||||||
(entity, Some(mut bloom)) => {
|
(entity, Some(mut bloom)) => {
|
||||||
*text = "Bloom (Toggle: Space)\n".to_string();
|
**text = "Bloom (Toggle: Space)\n".to_string();
|
||||||
text.push_str(&format!("(Q/A) Intensity: {}\n", bloom.intensity));
|
text.push_str(&format!("(Q/A) Intensity: {}\n", bloom.intensity));
|
||||||
text.push_str(&format!(
|
text.push_str(&format!(
|
||||||
"(W/S) Low-frequency boost: {}\n",
|
"(W/S) Low-frequency boost: {}\n",
|
||||||
|
@ -173,7 +173,7 @@ fn update_bloom_settings(
|
||||||
}
|
}
|
||||||
|
|
||||||
(entity, None) => {
|
(entity, None) => {
|
||||||
*text = "Bloom: Off (Toggle: Space)".to_string();
|
**text = "Bloom: Off (Toggle: Space)".to_string();
|
||||||
|
|
||||||
if keycode.just_pressed(KeyCode::Space) {
|
if keycode.just_pressed(KeyCode::Space) {
|
||||||
commands.entity(entity).insert(Bloom::default());
|
commands.entity(entity).insert(Bloom::default());
|
||||||
|
|
|
@ -78,7 +78,6 @@ fn update_text(mut text: Query<&mut Text>, cur_state: Res<State<Test>>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut text = text.single_mut();
|
let mut text = text.single_mut();
|
||||||
let text = &mut text.sections[0].value;
|
|
||||||
text.clear();
|
text.clear();
|
||||||
|
|
||||||
text.push_str("Intersection test:\n");
|
text.push_str("Intersection test:\n");
|
||||||
|
@ -272,14 +271,15 @@ fn setup(mut commands: Commands) {
|
||||||
Intersects::default(),
|
Intersects::default(),
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_filled_circle(gizmos: &mut Gizmos, position: Vec2, color: Srgba) {
|
fn draw_filled_circle(gizmos: &mut Gizmos, position: Vec2, color: Srgba) {
|
||||||
|
|
|
@ -134,17 +134,13 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// create a minimal UI explaining how to interact with the example
|
// create a minimal UI explaining how to interact with the example
|
||||||
commands.spawn(TextBundle {
|
commands.spawn((
|
||||||
text: Text::from_section(
|
Text::new("Left Arrow Key: Animate Left Sprite\nRight Arrow Key: Animate Right Sprite"),
|
||||||
"Left Arrow Key: Animate Left Sprite\nRight Arrow Key: Animate Right Sprite",
|
Style {
|
||||||
TextStyle::default(),
|
|
||||||
),
|
|
||||||
style: Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,12 +85,13 @@ fn spawn_sprites(
|
||||||
cmd.insert(scale_mode);
|
cmd.insert(scale_mode);
|
||||||
}
|
}
|
||||||
cmd.with_children(|builder| {
|
cmd.with_children(|builder| {
|
||||||
builder.spawn(Text2dBundle {
|
builder.spawn((
|
||||||
text: Text::from_section(label, text_style).with_justify(JustifyText::Center),
|
Text2d::new(label),
|
||||||
transform: Transform::from_xyz(0., -0.5 * size.y - 10., 0.0),
|
text_style,
|
||||||
text_anchor: bevy::sprite::Anchor::TopCenter,
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
..default()
|
Transform::from_xyz(0., -0.5 * size.y - 10., 0.0),
|
||||||
});
|
bevy::sprite::Anchor::TopCenter,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
position.x += 0.5 * size.x + gap;
|
position.x += 0.5 * size.x + gap;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,29 +45,24 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
// Demonstrate changing translation
|
// Demonstrate changing translation
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Text2dBundle {
|
Text2d::new("translation"),
|
||||||
text: Text::from_section("translation", text_style.clone())
|
text_style.clone(),
|
||||||
.with_justify(text_justification),
|
TextBlock::new_with_justify(text_justification),
|
||||||
..default()
|
|
||||||
},
|
|
||||||
AnimateTranslation,
|
AnimateTranslation,
|
||||||
));
|
));
|
||||||
// Demonstrate changing rotation
|
// Demonstrate changing rotation
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Text2dBundle {
|
Text2d::new("rotation"),
|
||||||
text: Text::from_section("rotation", text_style.clone())
|
text_style.clone(),
|
||||||
.with_justify(text_justification),
|
TextBlock::new_with_justify(text_justification),
|
||||||
..default()
|
|
||||||
},
|
|
||||||
AnimateRotation,
|
AnimateRotation,
|
||||||
));
|
));
|
||||||
// Demonstrate changing scale
|
// Demonstrate changing scale
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Text2dBundle {
|
Text2d::new("scale"),
|
||||||
text: Text::from_section("scale", text_style).with_justify(text_justification),
|
text_style,
|
||||||
transform: Transform::from_translation(Vec3::new(400.0, 0.0, 0.0)),
|
TextBlock::new_with_justify(text_justification),
|
||||||
..default()
|
Transform::from_translation(Vec3::new(400.0, 0.0, 0.0)),
|
||||||
},
|
|
||||||
AnimateScale,
|
AnimateScale,
|
||||||
));
|
));
|
||||||
// Demonstrate text wrapping
|
// Demonstrate text wrapping
|
||||||
|
@ -84,22 +79,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
Transform::from_translation(box_position.extend(0.0)),
|
Transform::from_translation(box_position.extend(0.0)),
|
||||||
))
|
))
|
||||||
.with_children(|builder| {
|
.with_children(|builder| {
|
||||||
builder.spawn(Text2dBundle {
|
builder.spawn((
|
||||||
text: Text {
|
Text2d::new("this text wraps in the box\n(Unicode linebreaks)"),
|
||||||
sections: vec![TextSection::new(
|
slightly_smaller_text_style.clone(),
|
||||||
"this text wraps in the box\n(Unicode linebreaks)",
|
TextBlock::new(JustifyText::Left, LineBreak::WordBoundary),
|
||||||
slightly_smaller_text_style.clone(),
|
|
||||||
)],
|
|
||||||
justify: JustifyText::Left,
|
|
||||||
linebreak: LineBreak::WordBoundary,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
// Wrap text in the rectangle
|
// Wrap text in the rectangle
|
||||||
text_2d_bounds: TextBounds::from(box_size),
|
TextBounds::from(box_size),
|
||||||
// ensure the text is drawn on top of the box
|
// ensure the text is drawn on top of the box
|
||||||
transform: Transform::from_translation(Vec3::Z),
|
Transform::from_translation(Vec3::Z),
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let other_box_size = Vec2::new(300.0, 200.0);
|
let other_box_size = Vec2::new(300.0, 200.0);
|
||||||
|
@ -110,31 +98,25 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
Transform::from_translation(other_box_position.extend(0.0)),
|
Transform::from_translation(other_box_position.extend(0.0)),
|
||||||
))
|
))
|
||||||
.with_children(|builder| {
|
.with_children(|builder| {
|
||||||
builder.spawn(Text2dBundle {
|
builder.spawn((
|
||||||
text: Text {
|
Text2d::new("this text wraps in the box\n(AnyCharacter linebreaks)"),
|
||||||
sections: vec![TextSection::new(
|
slightly_smaller_text_style.clone(),
|
||||||
"this text wraps in the box\n(AnyCharacter linebreaks)",
|
TextBlock::new(JustifyText::Left, LineBreak::AnyCharacter),
|
||||||
slightly_smaller_text_style.clone(),
|
|
||||||
)],
|
|
||||||
justify: JustifyText::Left,
|
|
||||||
linebreak: LineBreak::AnyCharacter,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
// Wrap text in the rectangle
|
// Wrap text in the rectangle
|
||||||
text_2d_bounds: TextBounds::from(other_box_size),
|
TextBounds::from(other_box_size),
|
||||||
// ensure the text is drawn on top of the box
|
// ensure the text is drawn on top of the box
|
||||||
transform: Transform::from_translation(Vec3::Z),
|
Transform::from_translation(Vec3::Z),
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Demonstrate font smoothing off
|
// Demonstrate font smoothing off
|
||||||
commands.spawn(Text2dBundle {
|
commands.spawn((
|
||||||
text: Text::from_section("FontSmoothing::None", slightly_smaller_text_style.clone())
|
Text2d::new("FontSmoothing::None"),
|
||||||
|
slightly_smaller_text_style
|
||||||
|
.clone()
|
||||||
.with_font_smoothing(FontSmoothing::None),
|
.with_font_smoothing(FontSmoothing::None),
|
||||||
transform: Transform::from_translation(Vec3::new(-400.0, -250.0, 0.0)),
|
Transform::from_translation(Vec3::new(-400.0, -250.0, 0.0)),
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
|
|
||||||
for (text_anchor, color) in [
|
for (text_anchor, color) in [
|
||||||
(Anchor::TopLeft, Color::Srgba(RED)),
|
(Anchor::TopLeft, Color::Srgba(RED)),
|
||||||
|
@ -142,27 +124,21 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
(Anchor::BottomRight, Color::Srgba(BLUE)),
|
(Anchor::BottomRight, Color::Srgba(BLUE)),
|
||||||
(Anchor::BottomLeft, Color::Srgba(YELLOW)),
|
(Anchor::BottomLeft, Color::Srgba(YELLOW)),
|
||||||
] {
|
] {
|
||||||
commands.spawn(Text2dBundle {
|
commands.spawn((
|
||||||
text: Text {
|
Text2d::new(format!(" Anchor::{text_anchor:?} ")),
|
||||||
sections: vec![TextSection::new(
|
TextStyle {
|
||||||
format!(" Anchor::{text_anchor:?} "),
|
color,
|
||||||
TextStyle {
|
..slightly_smaller_text_style.clone()
|
||||||
color,
|
|
||||||
..slightly_smaller_text_style.clone()
|
|
||||||
},
|
|
||||||
)],
|
|
||||||
..Default::default()
|
|
||||||
},
|
},
|
||||||
transform: Transform::from_translation(250. * Vec3::Y),
|
Transform::from_translation(250. * Vec3::Y),
|
||||||
text_anchor,
|
text_anchor,
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn animate_translation(
|
fn animate_translation(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut query: Query<&mut Transform, (With<Text>, With<AnimateTranslation>)>,
|
mut query: Query<&mut Transform, (With<Text2d>, With<AnimateTranslation>)>,
|
||||||
) {
|
) {
|
||||||
for mut transform in &mut query {
|
for mut transform in &mut query {
|
||||||
transform.translation.x = 100.0 * ops::sin(time.elapsed_seconds()) - 400.0;
|
transform.translation.x = 100.0 * ops::sin(time.elapsed_seconds()) - 400.0;
|
||||||
|
@ -172,7 +148,7 @@ fn animate_translation(
|
||||||
|
|
||||||
fn animate_rotation(
|
fn animate_rotation(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut query: Query<&mut Transform, (With<Text>, With<AnimateRotation>)>,
|
mut query: Query<&mut Transform, (With<Text2d>, With<AnimateRotation>)>,
|
||||||
) {
|
) {
|
||||||
for mut transform in &mut query {
|
for mut transform in &mut query {
|
||||||
transform.rotation = Quat::from_rotation_z(ops::cos(time.elapsed_seconds()));
|
transform.rotation = Quat::from_rotation_z(ops::cos(time.elapsed_seconds()));
|
||||||
|
@ -181,7 +157,7 @@ fn animate_rotation(
|
||||||
|
|
||||||
fn animate_scale(
|
fn animate_scale(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut query: Query<&mut Transform, (With<Text>, With<AnimateScale>)>,
|
mut query: Query<&mut Transform, (With<Text2d>, With<AnimateScale>)>,
|
||||||
) {
|
) {
|
||||||
// Consider changing font-size instead of scaling the transform. Scaling a Text2D will scale the
|
// Consider changing font-size instead of scaling the transform. Scaling a Text2D will scale the
|
||||||
// rendered quad, resulting in a pixellated look.
|
// rendered quad, resulting in a pixellated look.
|
||||||
|
|
|
@ -277,12 +277,13 @@ fn create_label(
|
||||||
text: &str,
|
text: &str,
|
||||||
text_style: TextStyle,
|
text_style: TextStyle,
|
||||||
) {
|
) {
|
||||||
commands.spawn(Text2dBundle {
|
commands.spawn((
|
||||||
text: Text::from_section(text, text_style).with_justify(JustifyText::Center),
|
Text2d::new(text),
|
||||||
transform: Transform {
|
text_style,
|
||||||
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
|
Transform {
|
||||||
translation: Vec3::new(translation.0, translation.1, translation.2),
|
translation: Vec3::new(translation.0, translation.1, translation.2),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,14 +88,15 @@ fn setup(
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
|
|
||||||
// Text used to show controls
|
// Text used to show controls
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This system lets you toggle various wireframe settings
|
/// This system lets you toggle various wireframe settings
|
||||||
|
@ -105,7 +106,7 @@ fn update_colors(
|
||||||
mut wireframe_colors: Query<&mut Wireframe2dColor>,
|
mut wireframe_colors: Query<&mut Wireframe2dColor>,
|
||||||
mut text: Query<&mut Text>,
|
mut text: Query<&mut Text>,
|
||||||
) {
|
) {
|
||||||
text.single_mut().sections[0].value = format!(
|
**text.single_mut() = format!(
|
||||||
"Controls
|
"Controls
|
||||||
---------------
|
---------------
|
||||||
Z - Toggle global
|
Z - Toggle global
|
||||||
|
|
|
@ -133,15 +133,15 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("Press space to toggle wireframes", TextStyle::default())
|
Text::new("Press space to toggle wireframes"),
|
||||||
.with_style(Style {
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) {
|
fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) {
|
||||||
|
|
|
@ -81,18 +81,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res
|
||||||
|
|
||||||
/// Spawns the help text.
|
/// Spawns the help text.
|
||||||
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
|
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
app_status.create_help_text(),
|
||||||
text: app_status.create_help_text(),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each material, creates a version with the anisotropy removed.
|
/// For each material, creates a version with the anisotropy removed.
|
||||||
|
@ -287,10 +284,10 @@ impl AppStatus {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build the `Text` object.
|
// Build the `Text` object.
|
||||||
Text::from_section(
|
Text(format!(
|
||||||
format!("{}\n{}", material_variant_help_text, light_help_text),
|
"{}\n{}",
|
||||||
TextStyle::default(),
|
material_variant_help_text, light_help_text
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -191,8 +191,7 @@ fn update_ui(
|
||||||
) {
|
) {
|
||||||
let (fxaa, smaa, taa, cas, msaa) = camera.single();
|
let (fxaa, smaa, taa, cas, msaa) = camera.single();
|
||||||
|
|
||||||
let mut ui = ui.single_mut();
|
let ui = &mut **ui.single_mut();
|
||||||
let ui = &mut ui.sections[0].value;
|
|
||||||
|
|
||||||
*ui = "Antialias Method\n".to_string();
|
*ui = "Antialias Method\n".to_string();
|
||||||
|
|
||||||
|
@ -328,14 +327,15 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// example instructions
|
// example instructions
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a simple menu item that can be on or off.
|
/// Writes a simple menu item that can be on or off.
|
||||||
|
|
|
@ -85,17 +85,13 @@ fn setup_terrain_scene(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_instructions(mut commands: Commands) {
|
fn setup_instructions(mut commands: Commands) {
|
||||||
commands.spawn(
|
commands.spawn((Text::new("Press Spacebar to Toggle Atmospheric Fog.\nPress S to Toggle Directional Light Fog Influence."),
|
||||||
TextBundle::from_section(
|
Style {
|
||||||
"Press Spacebar to Toggle Atmospheric Fog.\nPress S to Toggle Directional Light Fog Influence.",
|
|
||||||
TextStyle::default(),
|
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,26 +129,24 @@ fn setup(
|
||||||
|
|
||||||
let text_style = TextStyle::default();
|
let text_style = TextStyle::default();
|
||||||
|
|
||||||
commands.spawn(
|
commands.spawn((Text::new("Left / Right - Rotate Camera\nC - Toggle Compensation Curve\nM - Toggle Metering Mask\nV - Visualize Metering Mask"),
|
||||||
TextBundle::from_section(
|
text_style.clone(), Style {
|
||||||
"Left / Right - Rotate Camera\nC - Toggle Compensation Curve\nM - Toggle Metering Mask\nV - Visualize Metering Mask",
|
|
||||||
text_style.clone(),
|
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
TextBundle::from_section("", text_style).with_style(Style {
|
Text::default(),
|
||||||
|
text_style,
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
right: Val::Px(12.0),
|
right: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
ExampleDisplay,
|
ExampleDisplay,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -207,7 +205,7 @@ fn example_control_system(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut display = display.single_mut();
|
let mut display = display.single_mut();
|
||||||
display.sections[0].value = format!(
|
**display = format!(
|
||||||
"Compensation Curve: {}\nMetering Mask: {}",
|
"Compensation Curve: {}\nMetering Mask: {}",
|
||||||
if auto_exposure.compensation_curve == resources.basic_compensation_curve {
|
if auto_exposure.compensation_curve == resources.basic_compensation_curve {
|
||||||
"Enabled"
|
"Enabled"
|
||||||
|
|
|
@ -173,26 +173,25 @@ fn setup(
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
|
|
||||||
commands.spawn(
|
commands.spawn((Text::new("Up / Down — Increase / Decrease Alpha\nLeft / Right — Rotate Camera\nH - Toggle HDR\nSpacebar — Toggle Unlit\nC — Randomize Colors"),
|
||||||
TextBundle::from_section(
|
|
||||||
"Up / Down — Increase / Decrease Alpha\nLeft / Right — Rotate Camera\nH - Toggle HDR\nSpacebar — Toggle Unlit\nC — Randomize Colors",
|
|
||||||
text_style.clone(),
|
text_style.clone(),
|
||||||
)
|
Style {
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
TextBundle::from_section("", text_style).with_style(Style {
|
Text::default(),
|
||||||
|
text_style,
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
right: Val::Px(12.0),
|
right: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
ExampleDisplay,
|
ExampleDisplay,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -209,15 +208,16 @@ fn setup(
|
||||||
ExampleLabel { entity },
|
ExampleLabel { entity },
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(
|
parent.spawn((
|
||||||
TextBundle::from_section(label, label_text_style.clone())
|
Text::new(label),
|
||||||
.with_style(Style {
|
label_text_style.clone(),
|
||||||
position_type: PositionType::Absolute,
|
Style {
|
||||||
bottom: Val::ZERO,
|
position_type: PositionType::Absolute,
|
||||||
..default()
|
bottom: Val::ZERO,
|
||||||
})
|
..default()
|
||||||
.with_no_wrap(),
|
},
|
||||||
);
|
TextBlock::default().with_no_wrap(),
|
||||||
|
));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -328,7 +328,7 @@ fn example_control_system(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut display = display.single_mut();
|
let mut display = display.single_mut();
|
||||||
display.sections[0].value = format!(
|
**display = format!(
|
||||||
" HDR: {}\nAlpha: {:.2}",
|
" HDR: {}\nAlpha: {:.2}",
|
||||||
if camera.hdr { "ON " } else { "OFF" },
|
if camera.hdr { "ON " } else { "OFF" },
|
||||||
state.alpha
|
state.alpha
|
||||||
|
|
|
@ -84,14 +84,15 @@ fn setup_scene(
|
||||||
}
|
}
|
||||||
|
|
||||||
// example instructions
|
// example instructions
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
@ -105,11 +106,10 @@ fn update_bloom_settings(
|
||||||
) {
|
) {
|
||||||
let bloom = camera.single_mut();
|
let bloom = camera.single_mut();
|
||||||
let mut text = text.single_mut();
|
let mut text = text.single_mut();
|
||||||
let text = &mut text.sections[0].value;
|
|
||||||
|
|
||||||
match bloom {
|
match bloom {
|
||||||
(entity, Some(mut bloom)) => {
|
(entity, Some(mut bloom)) => {
|
||||||
*text = "Bloom (Toggle: Space)\n".to_string();
|
**text = "Bloom (Toggle: Space)\n".to_string();
|
||||||
text.push_str(&format!("(Q/A) Intensity: {}\n", bloom.intensity));
|
text.push_str(&format!("(Q/A) Intensity: {}\n", bloom.intensity));
|
||||||
text.push_str(&format!(
|
text.push_str(&format!(
|
||||||
"(W/S) Low-frequency boost: {}\n",
|
"(W/S) Low-frequency boost: {}\n",
|
||||||
|
@ -200,7 +200,7 @@ fn update_bloom_settings(
|
||||||
}
|
}
|
||||||
|
|
||||||
(entity, None) => {
|
(entity, None) => {
|
||||||
*text = "Bloom: Off (Toggle: Space)".to_string();
|
**text = "Bloom: Off (Toggle: Space)".to_string();
|
||||||
|
|
||||||
if keycode.just_pressed(KeyCode::Space) {
|
if keycode.just_pressed(KeyCode::Space) {
|
||||||
commands.entity(entity).insert(Bloom::NATURAL);
|
commands.entity(entity).insert(Bloom::NATURAL);
|
||||||
|
|
|
@ -217,18 +217,15 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
|
||||||
|
|
||||||
/// Spawns the help text.
|
/// Spawns the help text.
|
||||||
fn spawn_text(commands: &mut Commands, light_mode: &LightMode) {
|
fn spawn_text(commands: &mut Commands, light_mode: &LightMode) {
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
light_mode.create_help_text(),
|
||||||
text: light_mode.create_help_text(),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the light around.
|
/// Moves the light around.
|
||||||
|
@ -320,6 +317,6 @@ impl LightMode {
|
||||||
LightMode::Directional => "Press Space to switch to a point light",
|
LightMode::Directional => "Press Space to switch to a point light",
|
||||||
};
|
};
|
||||||
|
|
||||||
Text::from_section(help_text, TextStyle::default())
|
Text::new(help_text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,23 +314,20 @@ fn add_help_text(
|
||||||
font: &Handle<Font>,
|
font: &Handle<Font>,
|
||||||
currently_selected_option: &SelectedColorGradingOption,
|
currently_selected_option: &SelectedColorGradingOption,
|
||||||
) {
|
) {
|
||||||
commands
|
commands.spawn((
|
||||||
.spawn(TextBundle {
|
Text::new(create_help_text(currently_selected_option)),
|
||||||
style: Style {
|
TextStyle {
|
||||||
position_type: PositionType::Absolute,
|
font: font.clone(),
|
||||||
left: Val::Px(12.0),
|
..default()
|
||||||
top: Val::Px(12.0),
|
},
|
||||||
..default()
|
Style {
|
||||||
},
|
position_type: PositionType::Absolute,
|
||||||
..TextBundle::from_section(
|
left: Val::Px(12.0),
|
||||||
create_help_text(currently_selected_option),
|
top: Val::Px(12.0),
|
||||||
TextStyle {
|
..default()
|
||||||
font: font.clone(),
|
},
|
||||||
..default()
|
HelpText,
|
||||||
},
|
));
|
||||||
)
|
|
||||||
})
|
|
||||||
.insert(HelpText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds some text to the scene.
|
/// Adds some text to the scene.
|
||||||
|
@ -340,12 +337,13 @@ fn add_text<'a>(
|
||||||
font: &Handle<Font>,
|
font: &Handle<Font>,
|
||||||
color: Color,
|
color: Color,
|
||||||
) -> EntityCommands<'a> {
|
) -> EntityCommands<'a> {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn((
|
||||||
label,
|
Text::new(label),
|
||||||
TextStyle {
|
TextStyle {
|
||||||
font: font.clone(),
|
font: font.clone(),
|
||||||
font_size: 15.0,
|
font_size: 15.0,
|
||||||
color,
|
color,
|
||||||
|
..default()
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -561,8 +559,9 @@ fn update_ui_state(
|
||||||
&mut BorderColor,
|
&mut BorderColor,
|
||||||
&ColorGradingOptionWidget,
|
&ColorGradingOptionWidget,
|
||||||
)>,
|
)>,
|
||||||
mut button_text: Query<(&mut Text, &ColorGradingOptionWidget), Without<HelpText>>,
|
button_text: Query<(Entity, &ColorGradingOptionWidget), (With<Text>, Without<HelpText>)>,
|
||||||
mut help_text: Query<&mut Text, With<HelpText>>,
|
help_text: Query<Entity, With<HelpText>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
cameras: Query<Ref<ColorGrading>>,
|
cameras: Query<Ref<ColorGrading>>,
|
||||||
currently_selected_option: Res<SelectedColorGradingOption>,
|
currently_selected_option: Res<SelectedColorGradingOption>,
|
||||||
) {
|
) {
|
||||||
|
@ -590,7 +589,7 @@ fn update_ui_state(
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the buttons.
|
// Update the buttons.
|
||||||
for (mut text, widget) in button_text.iter_mut() {
|
for (entity, widget) in button_text.iter() {
|
||||||
// Set the text color.
|
// Set the text color.
|
||||||
|
|
||||||
let color = if *currently_selected_option == widget.option {
|
let color = if *currently_selected_option == widget.option {
|
||||||
|
@ -599,24 +598,24 @@ fn update_ui_state(
|
||||||
Color::WHITE
|
Color::WHITE
|
||||||
};
|
};
|
||||||
|
|
||||||
for section in &mut text.sections {
|
writer.for_each_style(entity, |mut style| {
|
||||||
section.style.color = color;
|
style.color = color;
|
||||||
}
|
});
|
||||||
|
|
||||||
// Update the displayed value, if this is the currently-selected option.
|
// Update the displayed value, if this is the currently-selected option.
|
||||||
if widget.widget_type == ColorGradingOptionWidgetType::Value
|
if widget.widget_type == ColorGradingOptionWidgetType::Value
|
||||||
&& *currently_selected_option == widget.option
|
&& *currently_selected_option == widget.option
|
||||||
{
|
{
|
||||||
if let Some(ref value_label) = value_label {
|
if let Some(ref value_label) = value_label {
|
||||||
for section in &mut text.sections {
|
writer.for_each_text(entity, |mut text| {
|
||||||
section.value.clone_from(value_label);
|
text.clone_from(value_label);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the help text.
|
// Update the help text.
|
||||||
help_text.single_mut().sections[0].value = create_help_text(¤tly_selected_option);
|
*writer.text(help_text.single(), 0) = create_help_text(¤tly_selected_option);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the help text at the top left of the window.
|
/// Creates the help text at the top left of the window.
|
||||||
|
|
|
@ -188,14 +188,15 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// Example instructions
|
// Example instructions
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
|
@ -292,7 +293,6 @@ fn switch_mode(
|
||||||
mut mode: Local<DefaultRenderMode>,
|
mut mode: Local<DefaultRenderMode>,
|
||||||
) {
|
) {
|
||||||
let mut text = text.single_mut();
|
let mut text = text.single_mut();
|
||||||
let text = &mut text.sections[0].value;
|
|
||||||
|
|
||||||
text.clear();
|
text.clear();
|
||||||
|
|
||||||
|
|
|
@ -92,18 +92,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: R
|
||||||
)));
|
)));
|
||||||
|
|
||||||
// Spawn the help text.
|
// Spawn the help text.
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
create_text(&app_settings),
|
||||||
text: create_text(&app_settings),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adjusts the focal distance and f-number per user inputs.
|
/// Adjusts the focal distance and f-number per user inputs.
|
||||||
|
@ -219,7 +216,7 @@ fn update_text(mut texts: Query<&mut Text>, app_settings: Res<AppSettings>) {
|
||||||
|
|
||||||
/// Regenerates the app text component per the current app settings.
|
/// Regenerates the app text component per the current app settings.
|
||||||
fn create_text(app_settings: &AppSettings) -> Text {
|
fn create_text(app_settings: &AppSettings) -> Text {
|
||||||
Text::from_section(app_settings.help_text(), TextStyle::default())
|
app_settings.help_text().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppSettings> for Option<DepthOfField> {
|
impl From<AppSettings> for Option<DepthOfField> {
|
||||||
|
|
|
@ -116,14 +116,15 @@ fn setup_pyramid_scene(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_instructions(mut commands: Commands) {
|
fn setup_instructions(mut commands: Commands) {
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_system(
|
fn update_system(
|
||||||
|
@ -148,12 +149,10 @@ fn update_system(
|
||||||
.looking_at(Vec3::ZERO, Vec3::Y);
|
.looking_at(Vec3::ZERO, Vec3::Y);
|
||||||
|
|
||||||
// Fog Information
|
// Fog Information
|
||||||
text.sections[0].value = format!("Fog Falloff: {:?}\nFog Color: {:?}", fog.falloff, fog.color);
|
**text = format!("Fog Falloff: {:?}\nFog Color: {:?}", fog.falloff, fog.color);
|
||||||
|
|
||||||
// Fog Falloff Mode Switching
|
// Fog Falloff Mode Switching
|
||||||
text.sections[0]
|
text.push_str("\n\n1 / 2 / 3 - Fog Falloff Mode");
|
||||||
.value
|
|
||||||
.push_str("\n\n1 / 2 / 3 - Fog Falloff Mode");
|
|
||||||
|
|
||||||
if keycode.pressed(KeyCode::Digit1) {
|
if keycode.pressed(KeyCode::Digit1) {
|
||||||
if let FogFalloff::Linear { .. } = fog.falloff {
|
if let FogFalloff::Linear { .. } = fog.falloff {
|
||||||
|
@ -192,9 +191,7 @@ fn update_system(
|
||||||
ref mut end,
|
ref mut end,
|
||||||
} = &mut fog.falloff
|
} = &mut fog.falloff
|
||||||
{
|
{
|
||||||
text.sections[0]
|
text.push_str("\nA / S - Move Start Distance\nZ / X - Move End Distance");
|
||||||
.value
|
|
||||||
.push_str("\nA / S - Move Start Distance\nZ / X - Move End Distance");
|
|
||||||
|
|
||||||
if keycode.pressed(KeyCode::KeyA) {
|
if keycode.pressed(KeyCode::KeyA) {
|
||||||
*start -= delta * 3.0;
|
*start -= delta * 3.0;
|
||||||
|
@ -212,7 +209,7 @@ fn update_system(
|
||||||
|
|
||||||
// Exponential Fog Controls
|
// Exponential Fog Controls
|
||||||
if let FogFalloff::Exponential { ref mut density } = &mut fog.falloff {
|
if let FogFalloff::Exponential { ref mut density } = &mut fog.falloff {
|
||||||
text.sections[0].value.push_str("\nA / S - Change Density");
|
text.push_str("\nA / S - Change Density");
|
||||||
|
|
||||||
if keycode.pressed(KeyCode::KeyA) {
|
if keycode.pressed(KeyCode::KeyA) {
|
||||||
*density -= delta * 0.5 * *density;
|
*density -= delta * 0.5 * *density;
|
||||||
|
@ -227,7 +224,7 @@ fn update_system(
|
||||||
|
|
||||||
// ExponentialSquared Fog Controls
|
// ExponentialSquared Fog Controls
|
||||||
if let FogFalloff::ExponentialSquared { ref mut density } = &mut fog.falloff {
|
if let FogFalloff::ExponentialSquared { ref mut density } = &mut fog.falloff {
|
||||||
text.sections[0].value.push_str("\nA / S - Change Density");
|
text.push_str("\nA / S - Change Density");
|
||||||
|
|
||||||
if keycode.pressed(KeyCode::KeyA) {
|
if keycode.pressed(KeyCode::KeyA) {
|
||||||
*density -= delta * 0.5 * *density;
|
*density -= delta * 0.5 * *density;
|
||||||
|
@ -241,9 +238,7 @@ fn update_system(
|
||||||
}
|
}
|
||||||
|
|
||||||
// RGBA Controls
|
// RGBA Controls
|
||||||
text.sections[0]
|
text.push_str("\n\n- / = - Red\n[ / ] - Green\n; / ' - Blue\n. / ? - Alpha");
|
||||||
.value
|
|
||||||
.push_str("\n\n- / = - Red\n[ / ] - Green\n; / ' - Blue\n. / ? - Alpha");
|
|
||||||
|
|
||||||
// We're performing various operations in the sRGB color space,
|
// We're performing various operations in the sRGB color space,
|
||||||
// so we convert the fog color to sRGB here, then modify it,
|
// so we convert the fog color to sRGB here, then modify it,
|
||||||
|
|
|
@ -57,18 +57,15 @@ fn setup(
|
||||||
commands.spawn((PointLight::default(), camera_and_light_transform));
|
commands.spawn((PointLight::default(), camera_and_light_transform));
|
||||||
|
|
||||||
// Text to describe the controls.
|
// Text to describe the controls.
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation"),
|
||||||
"Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation",
|
Style {
|
||||||
TextStyle::default(),
|
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// System to receive input from the user,
|
// System to receive input from the user,
|
||||||
|
|
|
@ -295,18 +295,15 @@ fn spawn_fox(commands: &mut Commands, assets: &ExampleAssets) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
|
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
app_status.create_text(),
|
||||||
text: app_status.create_text(),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// A system that updates the help text.
|
// A system that updates the help text.
|
||||||
|
@ -343,16 +340,14 @@ impl AppStatus {
|
||||||
ExampleModel::Fox => SWITCH_TO_SPHERE_HELP_TEXT,
|
ExampleModel::Fox => SWITCH_TO_SPHERE_HELP_TEXT,
|
||||||
};
|
};
|
||||||
|
|
||||||
Text::from_section(
|
format!(
|
||||||
format!(
|
"{CLICK_TO_MOVE_HELP_TEXT}
|
||||||
"{CLICK_TO_MOVE_HELP_TEXT}
|
|
||||||
{voxels_help_text}
|
{voxels_help_text}
|
||||||
{irradiance_volume_help_text}
|
{irradiance_volume_help_text}
|
||||||
{rotation_help_text}
|
{rotation_help_text}
|
||||||
{switch_mesh_help_text}"
|
{switch_mesh_help_text}"
|
||||||
),
|
|
||||||
TextStyle::default(),
|
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,41 +207,40 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// example instructions
|
// example instructions
|
||||||
let style = TextStyle::default();
|
|
||||||
|
|
||||||
commands.spawn(
|
commands
|
||||||
TextBundle::from_sections(vec![
|
.spawn((
|
||||||
TextSection::new(
|
Text::default(),
|
||||||
format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops),
|
Style {
|
||||||
style.clone(),
|
position_type: PositionType::Absolute,
|
||||||
),
|
top: Val::Px(12.0),
|
||||||
TextSection::new(
|
left: Val::Px(12.0),
|
||||||
format!(
|
..default()
|
||||||
"Shutter speed: 1/{:.0}s\n",
|
},
|
||||||
1.0 / parameters.shutter_speed_s
|
))
|
||||||
),
|
.with_children(|p| {
|
||||||
style.clone(),
|
p.spawn(TextSpan(format!(
|
||||||
),
|
"Aperture: f/{:.0}\n",
|
||||||
TextSection::new(
|
parameters.aperture_f_stops,
|
||||||
format!("Sensitivity: ISO {:.0}\n", parameters.sensitivity_iso),
|
)));
|
||||||
style.clone(),
|
p.spawn(TextSpan(format!(
|
||||||
),
|
"Shutter speed: 1/{:.0}s\n",
|
||||||
TextSection::new("\n\n", style.clone()),
|
1.0 / parameters.shutter_speed_s
|
||||||
TextSection::new("Controls\n", style.clone()),
|
)));
|
||||||
TextSection::new("---------------\n", style.clone()),
|
p.spawn(TextSpan(format!(
|
||||||
TextSection::new("Arrow keys - Move objects\n", style.clone()),
|
"Sensitivity: ISO {:.0}\n",
|
||||||
TextSection::new("1/2 - Decrease/Increase aperture\n", style.clone()),
|
parameters.sensitivity_iso
|
||||||
TextSection::new("3/4 - Decrease/Increase shutter speed\n", style.clone()),
|
)));
|
||||||
TextSection::new("5/6 - Decrease/Increase sensitivity\n", style.clone()),
|
p.spawn(TextSpan::new("\n\n"));
|
||||||
TextSection::new("R - Reset exposure", style),
|
p.spawn(TextSpan::new("Controls\n"));
|
||||||
])
|
p.spawn(TextSpan::new("---------------\n"));
|
||||||
.with_style(Style {
|
p.spawn(TextSpan::new("Arrow keys - Move objects\n"));
|
||||||
position_type: PositionType::Absolute,
|
p.spawn(TextSpan::new("1/2 - Decrease/Increase aperture\n"));
|
||||||
top: Val::Px(12.0),
|
p.spawn(TextSpan::new("Arrow keys - Move objects\n"));
|
||||||
left: Val::Px(12.0),
|
p.spawn(TextSpan::new("3/4 - Decrease/Increase shutter speed\n"));
|
||||||
..default()
|
p.spawn(TextSpan::new("5/6 - Decrease/Increase sensitivity\n"));
|
||||||
}),
|
p.spawn(TextSpan::new("R - Reset exposure"));
|
||||||
);
|
});
|
||||||
|
|
||||||
// camera
|
// camera
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
|
@ -255,10 +254,11 @@ fn update_exposure(
|
||||||
key_input: Res<ButtonInput<KeyCode>>,
|
key_input: Res<ButtonInput<KeyCode>>,
|
||||||
mut parameters: ResMut<Parameters>,
|
mut parameters: ResMut<Parameters>,
|
||||||
mut exposure: Query<&mut Exposure>,
|
mut exposure: Query<&mut Exposure>,
|
||||||
mut text: Query<&mut Text>,
|
text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
// TODO: Clamp values to a reasonable range
|
// TODO: Clamp values to a reasonable range
|
||||||
let mut text = text.single_mut();
|
let entity = text.single();
|
||||||
if key_input.just_pressed(KeyCode::Digit2) {
|
if key_input.just_pressed(KeyCode::Digit2) {
|
||||||
parameters.aperture_f_stops *= 2.0;
|
parameters.aperture_f_stops *= 2.0;
|
||||||
} else if key_input.just_pressed(KeyCode::Digit1) {
|
} else if key_input.just_pressed(KeyCode::Digit1) {
|
||||||
|
@ -278,12 +278,12 @@ fn update_exposure(
|
||||||
*parameters = Parameters::default();
|
*parameters = Parameters::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
text.sections[0].value = format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops);
|
*writer.text(entity, 1) = format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops);
|
||||||
text.sections[1].value = format!(
|
*writer.text(entity, 2) = format!(
|
||||||
"Shutter speed: 1/{:.0}s\n",
|
"Shutter speed: 1/{:.0}s\n",
|
||||||
1.0 / parameters.shutter_speed_s
|
1.0 / parameters.shutter_speed_s
|
||||||
);
|
);
|
||||||
text.sections[2].value = format!("Sensitivity: ISO {:.0}\n", parameters.sensitivity_iso);
|
*writer.text(entity, 3) = format!("Sensitivity: ISO {:.0}\n", parameters.sensitivity_iso);
|
||||||
|
|
||||||
*exposure.single_mut() = Exposure::from_physical_camera(**parameters);
|
*exposure.single_mut() = Exposure::from_physical_camera(**parameters);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,19 +34,17 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
|
||||||
// a place to display the extras on screen
|
// a place to display the extras on screen
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::default(),
|
||||||
"",
|
TextStyle {
|
||||||
TextStyle {
|
font_size: 15.,
|
||||||
font_size: 15.,
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
Style {
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
ExampleDisplay,
|
ExampleDisplay,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -88,7 +86,6 @@ fn check_for_gltf_extras(
|
||||||
);
|
);
|
||||||
gltf_extra_infos_lines.push(formatted_extras);
|
gltf_extra_infos_lines.push(formatted_extras);
|
||||||
}
|
}
|
||||||
let mut display = display.single_mut();
|
**display.single_mut() = gltf_extra_infos_lines.join("\n");
|
||||||
display.sections[0].value = gltf_extra_infos_lines.join("\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,29 +231,30 @@ fn spawn_trees(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_ui(mut commands: Commands) {
|
fn setup_ui(mut commands: Commands) {
|
||||||
let style = TextStyle::default();
|
commands
|
||||||
|
.spawn((
|
||||||
commands.spawn(
|
Text::default(),
|
||||||
TextBundle::from_sections(vec![
|
Style {
|
||||||
TextSection::new(String::new(), style.clone()),
|
position_type: PositionType::Absolute,
|
||||||
TextSection::new(String::new(), style.clone()),
|
top: Val::Px(12.0),
|
||||||
TextSection::new("1/2: -/+ shutter angle (blur amount)\n", style.clone()),
|
left: Val::Px(12.0),
|
||||||
TextSection::new("3/4: -/+ sample count (blur quality)\n", style.clone()),
|
..default()
|
||||||
TextSection::new("Spacebar: cycle camera\n", style.clone()),
|
},
|
||||||
])
|
))
|
||||||
.with_style(Style {
|
.with_children(|p| {
|
||||||
position_type: PositionType::Absolute,
|
p.spawn(TextSpan::default());
|
||||||
top: Val::Px(12.0),
|
p.spawn(TextSpan::default());
|
||||||
left: Val::Px(12.0),
|
p.spawn(TextSpan::new("1/2: -/+ shutter angle (blur amount)\n"));
|
||||||
..default()
|
p.spawn(TextSpan::new("3/4: -/+ sample count (blur quality)\n"));
|
||||||
}),
|
p.spawn(TextSpan::new("3/4: -/+ sample count (blur quality)\n"));
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyboard_inputs(
|
fn keyboard_inputs(
|
||||||
mut motion_blur: Query<&mut MotionBlur>,
|
mut motion_blur: Query<&mut MotionBlur>,
|
||||||
presses: Res<ButtonInput<KeyCode>>,
|
presses: Res<ButtonInput<KeyCode>>,
|
||||||
mut text: Query<&mut Text>,
|
text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
mut camera: ResMut<CameraMode>,
|
mut camera: ResMut<CameraMode>,
|
||||||
) {
|
) {
|
||||||
let mut motion_blur = motion_blur.single_mut();
|
let mut motion_blur = motion_blur.single_mut();
|
||||||
|
@ -273,9 +274,9 @@ fn keyboard_inputs(
|
||||||
}
|
}
|
||||||
motion_blur.shutter_angle = motion_blur.shutter_angle.clamp(0.0, 1.0);
|
motion_blur.shutter_angle = motion_blur.shutter_angle.clamp(0.0, 1.0);
|
||||||
motion_blur.samples = motion_blur.samples.clamp(0, 64);
|
motion_blur.samples = motion_blur.samples.clamp(0, 64);
|
||||||
let mut text = text.single_mut();
|
let entity = text.single();
|
||||||
text.sections[0].value = format!("Shutter angle: {:.2}\n", motion_blur.shutter_angle);
|
*writer.text(entity, 1) = format!("Shutter angle: {:.2}\n", motion_blur.shutter_angle);
|
||||||
text.sections[1].value = format!("Samples: {:.5}\n", motion_blur.samples);
|
*writer.text(entity, 2) = format!("Samples: {:.5}\n", motion_blur.samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parametric function for a looping race track. `offset` will return the point offset
|
/// Parametric function for a looping race track. `offset` will return the point offset
|
||||||
|
|
|
@ -50,14 +50,13 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// spawn help text
|
// spawn help text
|
||||||
commands.spawn((
|
commands
|
||||||
TextBundle::from_sections([
|
.spawn((Text::default(), RenderLayers::layer(1)))
|
||||||
TextSection::new("Press T to toggle OIT\n", TextStyle::default()),
|
.with_children(|p| {
|
||||||
TextSection::new("OIT Enabled", TextStyle::default()),
|
p.spawn(TextSpan::new("Press T to toggle OIT\n"));
|
||||||
TextSection::new("\nPress C to cycle test scenes", TextStyle::default()),
|
p.spawn(TextSpan::new("OIT Enabled"));
|
||||||
]),
|
p.spawn(TextSpan::new("\nPress C to cycle test scenes"));
|
||||||
RenderLayers::layer(1),
|
});
|
||||||
));
|
|
||||||
|
|
||||||
// spawn default scene
|
// spawn default scene
|
||||||
spawn_spheres(&mut commands, &mut meshes, &mut materials);
|
spawn_spheres(&mut commands, &mut meshes, &mut materials);
|
||||||
|
@ -65,13 +64,14 @@ fn setup(
|
||||||
|
|
||||||
fn toggle_oit(
|
fn toggle_oit(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut text: Query<&mut Text>,
|
text: Single<Entity, With<Text>>,
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
q: Query<(Entity, Has<OrderIndependentTransparencySettings>), With<Camera3d>>,
|
q: Query<(Entity, Has<OrderIndependentTransparencySettings>), With<Camera3d>>,
|
||||||
|
mut text_writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
if keyboard_input.just_pressed(KeyCode::KeyT) {
|
if keyboard_input.just_pressed(KeyCode::KeyT) {
|
||||||
let (e, has_oit) = q.single();
|
let (e, has_oit) = q.single();
|
||||||
text.single_mut().sections[1].value = if has_oit {
|
*text_writer.text(*text, 2) = if has_oit {
|
||||||
// Removing the component will completely disable OIT for this camera
|
// Removing the component will completely disable OIT for this camera
|
||||||
commands
|
commands
|
||||||
.entity(e)
|
.entity(e)
|
||||||
|
|
|
@ -80,7 +80,8 @@ fn update_parallax_depth_scale(
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
mut target_depth: Local<TargetDepth>,
|
mut target_depth: Local<TargetDepth>,
|
||||||
mut depth_update: Local<bool>,
|
mut depth_update: Local<bool>,
|
||||||
mut text: Query<&mut Text>,
|
mut writer: UiTextWriter,
|
||||||
|
text: Query<Entity, With<Text>>,
|
||||||
) {
|
) {
|
||||||
if input.just_pressed(KeyCode::Digit1) {
|
if input.just_pressed(KeyCode::Digit1) {
|
||||||
target_depth.0 -= DEPTH_UPDATE_STEP;
|
target_depth.0 -= DEPTH_UPDATE_STEP;
|
||||||
|
@ -93,12 +94,11 @@ fn update_parallax_depth_scale(
|
||||||
*depth_update = true;
|
*depth_update = true;
|
||||||
}
|
}
|
||||||
if *depth_update {
|
if *depth_update {
|
||||||
let mut text = text.single_mut();
|
|
||||||
for (_, mat) in materials.iter_mut() {
|
for (_, mat) in materials.iter_mut() {
|
||||||
let current_depth = mat.parallax_depth_scale;
|
let current_depth = mat.parallax_depth_scale;
|
||||||
let new_depth = current_depth.lerp(target_depth.0, DEPTH_CHANGE_RATE);
|
let new_depth = current_depth.lerp(target_depth.0, DEPTH_CHANGE_RATE);
|
||||||
mat.parallax_depth_scale = new_depth;
|
mat.parallax_depth_scale = new_depth;
|
||||||
text.sections[0].value = format!("Parallax depth scale: {new_depth:.5}\n");
|
*writer.text(text.single(), 1) = format!("Parallax depth scale: {new_depth:.5}\n");
|
||||||
if (new_depth - current_depth).abs() <= 0.000000001 {
|
if (new_depth - current_depth).abs() <= 0.000000001 {
|
||||||
*depth_update = false;
|
*depth_update = false;
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,8 @@ fn update_parallax_depth_scale(
|
||||||
fn switch_method(
|
fn switch_method(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
mut text: Query<&mut Text>,
|
text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
mut current: Local<CurrentMethod>,
|
mut current: Local<CurrentMethod>,
|
||||||
) {
|
) {
|
||||||
if input.just_pressed(KeyCode::Space) {
|
if input.just_pressed(KeyCode::Space) {
|
||||||
|
@ -117,8 +118,8 @@ fn switch_method(
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut text = text.single_mut();
|
let text_entity = text.single();
|
||||||
text.sections[2].value = format!("Method: {}\n", *current);
|
*writer.text(text_entity, 3) = format!("Method: {}\n", *current);
|
||||||
|
|
||||||
for (_, mat) in materials.iter_mut() {
|
for (_, mat) in materials.iter_mut() {
|
||||||
mat.parallax_mapping_method = current.0;
|
mat.parallax_mapping_method = current.0;
|
||||||
|
@ -129,7 +130,8 @@ fn update_parallax_layers(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
mut target_layers: Local<TargetLayers>,
|
mut target_layers: Local<TargetLayers>,
|
||||||
mut text: Query<&mut Text>,
|
text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
if input.just_pressed(KeyCode::Digit3) {
|
if input.just_pressed(KeyCode::Digit3) {
|
||||||
target_layers.0 -= 1.0;
|
target_layers.0 -= 1.0;
|
||||||
|
@ -140,8 +142,8 @@ fn update_parallax_layers(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let layer_count = ops::exp2(target_layers.0);
|
let layer_count = ops::exp2(target_layers.0);
|
||||||
let mut text = text.single_mut();
|
let text_entity = text.single();
|
||||||
text.sections[1].value = format!("Layers: {layer_count:.0}\n");
|
*writer.text(text_entity, 2) = format!("Layers: {layer_count:.0}\n");
|
||||||
|
|
||||||
for (_, mat) in materials.iter_mut() {
|
for (_, mat) in materials.iter_mut() {
|
||||||
mat.max_parallax_layer_count = layer_count;
|
mat.max_parallax_layer_count = layer_count;
|
||||||
|
@ -293,35 +295,30 @@ fn setup(
|
||||||
commands.spawn(background_cube_bundle(Vec3::new(0., 0., 45.)));
|
commands.spawn(background_cube_bundle(Vec3::new(0., 0., 45.)));
|
||||||
commands.spawn(background_cube_bundle(Vec3::new(0., 0., -45.)));
|
commands.spawn(background_cube_bundle(Vec3::new(0., 0., -45.)));
|
||||||
|
|
||||||
let style = TextStyle::default();
|
|
||||||
|
|
||||||
// example instructions
|
// example instructions
|
||||||
commands.spawn(
|
commands
|
||||||
TextBundle::from_sections(vec![
|
.spawn((
|
||||||
TextSection::new(
|
Text::default(),
|
||||||
format!("Parallax depth scale: {parallax_depth_scale:.5}\n"),
|
Style {
|
||||||
style.clone(),
|
position_type: PositionType::Absolute,
|
||||||
),
|
top: Val::Px(12.0),
|
||||||
TextSection::new(
|
left: Val::Px(12.0),
|
||||||
format!("Layers: {max_parallax_layer_count:.0}\n"),
|
..default()
|
||||||
style.clone(),
|
},
|
||||||
),
|
))
|
||||||
TextSection::new(format!("{parallax_mapping_method}\n"), style.clone()),
|
.with_children(|p| {
|
||||||
TextSection::new("\n\n", style.clone()),
|
p.spawn(TextSpan(format!(
|
||||||
TextSection::new("Controls:\n", style.clone()),
|
"Parallax depth scale: {parallax_depth_scale:.5}\n"
|
||||||
TextSection::new("Left click - Change view angle\n", style.clone()),
|
)));
|
||||||
TextSection::new(
|
p.spawn(TextSpan(format!("Layers: {max_parallax_layer_count:.0}\n")));
|
||||||
|
p.spawn(TextSpan(format!("{parallax_mapping_method}\n")));
|
||||||
|
p.spawn(TextSpan::new("\n\n"));
|
||||||
|
p.spawn(TextSpan::new("Controls:\n"));
|
||||||
|
p.spawn(TextSpan::new("Left click - Change view angle\n"));
|
||||||
|
p.spawn(TextSpan::new(
|
||||||
"1/2 - Decrease/Increase parallax depth scale\n",
|
"1/2 - Decrease/Increase parallax depth scale\n",
|
||||||
style.clone(),
|
));
|
||||||
),
|
p.spawn(TextSpan::new("3/4 - Decrease/Increase layer count\n"));
|
||||||
TextSection::new("3/4 - Decrease/Increase layer count\n", style.clone()),
|
p.spawn(TextSpan::new("Space - Switch parallaxing algorithm\n"));
|
||||||
TextSection::new("Space - Switch parallaxing algorithm\n", style),
|
});
|
||||||
])
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
top: Val::Px(12.0),
|
|
||||||
left: Val::Px(12.0),
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,57 +58,50 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// labels
|
// labels
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Perceptual Roughness"),
|
||||||
"Perceptual Roughness",
|
TextStyle {
|
||||||
TextStyle {
|
font_size: 30.0,
|
||||||
font_size: 30.0,
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
Style {
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(20.0),
|
top: Val::Px(20.0),
|
||||||
left: Val::Px(100.0),
|
left: Val::Px(100.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
commands.spawn(TextBundle {
|
commands.spawn((
|
||||||
text: Text::from_section(
|
Text::new("Metallic"),
|
||||||
"Metallic",
|
TextStyle {
|
||||||
TextStyle {
|
font_size: 30.0,
|
||||||
font_size: 30.0,
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
Style {
|
||||||
),
|
|
||||||
style: Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(130.0),
|
top: Val::Px(130.0),
|
||||||
right: Val::ZERO,
|
right: Val::ZERO,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
transform: Transform {
|
Transform {
|
||||||
rotation: Quat::from_rotation_z(std::f32::consts::PI / 2.0),
|
rotation: Quat::from_rotation_z(std::f32::consts::PI / 2.0),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Loading Environment Map..."),
|
||||||
"Loading Environment Map...",
|
TextStyle {
|
||||||
TextStyle {
|
font_size: 30.0,
|
||||||
font_size: 30.0,
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
Style {
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(20.0),
|
bottom: Val::Px(20.0),
|
||||||
right: Val::Px(20.0),
|
right: Val::Px(20.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
EnvironmentMapLabel,
|
EnvironmentMapLabel,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -250,15 +250,17 @@ fn spawn_buttons(commands: &mut Commands) {
|
||||||
fn update_radio_buttons(
|
fn update_radio_buttons(
|
||||||
mut widgets: Query<
|
mut widgets: Query<
|
||||||
(
|
(
|
||||||
|
Entity,
|
||||||
Option<&mut BackgroundColor>,
|
Option<&mut BackgroundColor>,
|
||||||
Option<&mut Text>,
|
Has<Text>,
|
||||||
&WidgetClickSender<AppSetting>,
|
&WidgetClickSender<AppSetting>,
|
||||||
),
|
),
|
||||||
Or<(With<RadioButton>, With<RadioButtonText>)>,
|
Or<(With<RadioButton>, With<RadioButtonText>)>,
|
||||||
>,
|
>,
|
||||||
app_status: Res<AppStatus>,
|
app_status: Res<AppStatus>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
for (image, text, sender) in widgets.iter_mut() {
|
for (entity, image, has_text, sender) in widgets.iter_mut() {
|
||||||
let selected = match **sender {
|
let selected = match **sender {
|
||||||
AppSetting::LightType(light_type) => light_type == app_status.light_type,
|
AppSetting::LightType(light_type) => light_type == app_status.light_type,
|
||||||
AppSetting::ShadowFilter(shadow_filter) => shadow_filter == app_status.shadow_filter,
|
AppSetting::ShadowFilter(shadow_filter) => shadow_filter == app_status.shadow_filter,
|
||||||
|
@ -268,8 +270,8 @@ fn update_radio_buttons(
|
||||||
if let Some(mut bg_color) = image {
|
if let Some(mut bg_color) = image {
|
||||||
widgets::update_ui_radio_button(&mut bg_color, selected);
|
widgets::update_ui_radio_button(&mut bg_color, selected);
|
||||||
}
|
}
|
||||||
if let Some(mut text) = text {
|
if has_text {
|
||||||
widgets::update_ui_radio_button_text(&mut text, selected);
|
widgets::update_ui_radio_button_text(entity, &mut writer, selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,18 +122,15 @@ fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
|
||||||
|
|
||||||
/// Spawns the help text at the bottom of the screen.
|
/// Spawns the help text at the bottom of the screen.
|
||||||
fn spawn_text(commands: &mut Commands, app_settings: &AppSettings) {
|
fn spawn_text(commands: &mut Commands, app_settings: &AppSettings) {
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
create_help_text(app_settings),
|
||||||
text: create_help_text(app_settings),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppSettings {
|
impl Default for AppSettings {
|
||||||
|
@ -146,13 +143,11 @@ impl Default for AppSettings {
|
||||||
|
|
||||||
/// Creates help text at the bottom of the screen.
|
/// Creates help text at the bottom of the screen.
|
||||||
fn create_help_text(app_settings: &AppSettings) -> Text {
|
fn create_help_text(app_settings: &AppSettings) -> Text {
|
||||||
Text::from_section(
|
format!(
|
||||||
format!(
|
"Chromatic aberration intensity: {} (Press Left or Right to change)",
|
||||||
"Chromatic aberration intensity: {} (Press Left or Right to change)",
|
app_settings.chromatic_aberration_intensity
|
||||||
app_settings.chromatic_aberration_intensity
|
|
||||||
),
|
|
||||||
TextStyle::default(),
|
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles requests from the user to change the chromatic aberration intensity.
|
/// Handles requests from the user to change the chromatic aberration intensity.
|
||||||
|
|
|
@ -152,18 +152,15 @@ fn spawn_reflection_probe(commands: &mut Commands, cubemaps: &Cubemaps) {
|
||||||
// Spawns the help text.
|
// Spawns the help text.
|
||||||
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
|
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
|
||||||
// Create the text.
|
// Create the text.
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
app_status.create_text(),
|
||||||
text: app_status.create_text(),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a world environment map to the camera. This separate system is needed because the camera is
|
// Adds a world environment map to the camera. This separate system is needed because the camera is
|
||||||
|
@ -276,13 +273,11 @@ impl AppStatus {
|
||||||
START_ROTATION_HELP_TEXT
|
START_ROTATION_HELP_TEXT
|
||||||
};
|
};
|
||||||
|
|
||||||
Text::from_section(
|
format!(
|
||||||
format!(
|
"{}\n{}\n{}",
|
||||||
"{}\n{}\n{}",
|
self.reflection_mode, rotation_help_text, REFLECTION_MODE_HELP_TEXT
|
||||||
self.reflection_mode, rotation_help_text, REFLECTION_MODE_HELP_TEXT
|
|
||||||
),
|
|
||||||
TextStyle::default(),
|
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,8 +102,6 @@ fn setup(
|
||||||
MeshMaterial3d(white_handle),
|
MeshMaterial3d(white_handle),
|
||||||
));
|
));
|
||||||
|
|
||||||
let style = TextStyle::default();
|
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
NodeBundle {
|
NodeBundle {
|
||||||
|
@ -117,55 +115,42 @@ fn setup(
|
||||||
},
|
},
|
||||||
GlobalZIndex(i32::MAX),
|
GlobalZIndex(i32::MAX),
|
||||||
))
|
))
|
||||||
.with_children(|c| {
|
.with_children(|p| {
|
||||||
c.spawn(TextBundle::from_sections([
|
p.spawn(Text::default()).with_children(|p| {
|
||||||
TextSection::new("Controls:\n", style.clone()),
|
p.spawn(TextSpan::new("Controls:\n"));
|
||||||
TextSection::new("R / Z - reset biases to default / zero\n", style.clone()),
|
p.spawn(TextSpan::new("R / Z - reset biases to default / zero\n"));
|
||||||
TextSection::new(
|
p.spawn(TextSpan::new(
|
||||||
"L - switch between directional and point lights [",
|
"L - switch between directional and point lights [",
|
||||||
style.clone(),
|
));
|
||||||
),
|
p.spawn(TextSpan::new("DirectionalLight"));
|
||||||
TextSection::new("DirectionalLight", style.clone()),
|
p.spawn(TextSpan::new("]\n"));
|
||||||
TextSection::new("]\n", style.clone()),
|
p.spawn(TextSpan::new(
|
||||||
TextSection::new(
|
|
||||||
"F - switch directional light filter methods [",
|
"F - switch directional light filter methods [",
|
||||||
style.clone(),
|
));
|
||||||
),
|
p.spawn(TextSpan::new("Hardware2x2"));
|
||||||
TextSection::new("Hardware2x2", style.clone()),
|
p.spawn(TextSpan::new("]\n"));
|
||||||
TextSection::new("]\n", style.clone()),
|
p.spawn(TextSpan::new("1/2 - change point light depth bias ["));
|
||||||
TextSection::new("1/2 - change point light depth bias [", style.clone()),
|
p.spawn(TextSpan::new("0.00"));
|
||||||
TextSection::new("0.00", style.clone()),
|
p.spawn(TextSpan::new("]\n"));
|
||||||
TextSection::new("]\n", style.clone()),
|
p.spawn(TextSpan::new("3/4 - change point light normal bias ["));
|
||||||
TextSection::new("3/4 - change point light normal bias [", style.clone()),
|
p.spawn(TextSpan::new("0.0"));
|
||||||
TextSection::new("0.0", style.clone()),
|
p.spawn(TextSpan::new("]\n"));
|
||||||
TextSection::new("]\n", style.clone()),
|
p.spawn(TextSpan::new("5/6 - change direction light depth bias ["));
|
||||||
TextSection::new("5/6 - change direction light depth bias [", style.clone()),
|
p.spawn(TextSpan::new("0.00"));
|
||||||
TextSection::new("0.00", style.clone()),
|
p.spawn(TextSpan::new("]\n"));
|
||||||
TextSection::new("]\n", style.clone()),
|
p.spawn(TextSpan::new(
|
||||||
TextSection::new(
|
|
||||||
"7/8 - change direction light normal bias [",
|
"7/8 - change direction light normal bias [",
|
||||||
style.clone(),
|
));
|
||||||
),
|
p.spawn(TextSpan::new("0.0"));
|
||||||
TextSection::new("0.0", style.clone()),
|
p.spawn(TextSpan::new("]\n"));
|
||||||
TextSection::new("]\n", style.clone()),
|
p.spawn(TextSpan::new(
|
||||||
TextSection::new(
|
|
||||||
"left/right/up/down/pgup/pgdown - adjust light position (looking at 0,0,0) [",
|
"left/right/up/down/pgup/pgdown - adjust light position (looking at 0,0,0) [",
|
||||||
style.clone(),
|
));
|
||||||
),
|
p.spawn(TextSpan(format!("{:.1},", light_transform.translation.x)));
|
||||||
TextSection::new(
|
p.spawn(TextSpan(format!(" {:.1},", light_transform.translation.y)));
|
||||||
format!("{:.1},", light_transform.translation.x),
|
p.spawn(TextSpan(format!(" {:.1}", light_transform.translation.z)));
|
||||||
style.clone(),
|
p.spawn(TextSpan::new("]\n"));
|
||||||
),
|
});
|
||||||
TextSection::new(
|
|
||||||
format!(" {:.1},", light_transform.translation.y),
|
|
||||||
style.clone(),
|
|
||||||
),
|
|
||||||
TextSection::new(
|
|
||||||
format!(" {:.1}", light_transform.translation.z),
|
|
||||||
style.clone(),
|
|
||||||
),
|
|
||||||
TextSection::new("]\n", style.clone()),
|
|
||||||
]));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,12 +158,13 @@ fn toggle_light(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
mut point_lights: Query<&mut PointLight>,
|
mut point_lights: Query<&mut PointLight>,
|
||||||
mut directional_lights: Query<&mut DirectionalLight>,
|
mut directional_lights: Query<&mut DirectionalLight>,
|
||||||
mut example_text: Query<&mut Text>,
|
example_text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
if input.just_pressed(KeyCode::KeyL) {
|
if input.just_pressed(KeyCode::KeyL) {
|
||||||
for mut light in &mut point_lights {
|
for mut light in &mut point_lights {
|
||||||
light.intensity = if light.intensity == 0.0 {
|
light.intensity = if light.intensity == 0.0 {
|
||||||
example_text.single_mut().sections[3].value = "PointLight".to_string();
|
*writer.text(example_text.single(), 4) = "PointLight".to_string();
|
||||||
100000000.0
|
100000000.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
|
@ -186,7 +172,7 @@ fn toggle_light(
|
||||||
}
|
}
|
||||||
for mut light in &mut directional_lights {
|
for mut light in &mut directional_lights {
|
||||||
light.illuminance = if light.illuminance == 0.0 {
|
light.illuminance = if light.illuminance == 0.0 {
|
||||||
example_text.single_mut().sections[3].value = "DirectionalLight".to_string();
|
*writer.text(example_text.single(), 4) = "DirectionalLight".to_string();
|
||||||
100000.0
|
100000.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
|
@ -198,7 +184,8 @@ fn toggle_light(
|
||||||
fn adjust_light_position(
|
fn adjust_light_position(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
mut lights: Query<&mut Transform, With<Lights>>,
|
mut lights: Query<&mut Transform, With<Lights>>,
|
||||||
mut example_text: Query<&mut Text>,
|
example_text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
let mut offset = Vec3::ZERO;
|
let mut offset = Vec3::ZERO;
|
||||||
if input.just_pressed(KeyCode::ArrowLeft) {
|
if input.just_pressed(KeyCode::ArrowLeft) {
|
||||||
|
@ -220,13 +207,13 @@ fn adjust_light_position(
|
||||||
offset.y += 1.0;
|
offset.y += 1.0;
|
||||||
}
|
}
|
||||||
if offset != Vec3::ZERO {
|
if offset != Vec3::ZERO {
|
||||||
let mut example_text = example_text.single_mut();
|
let example_text = example_text.single();
|
||||||
for mut light in &mut lights {
|
for mut light in &mut lights {
|
||||||
light.translation += offset;
|
light.translation += offset;
|
||||||
light.look_at(Vec3::ZERO, Vec3::Y);
|
light.look_at(Vec3::ZERO, Vec3::Y);
|
||||||
example_text.sections[21].value = format!("{:.1},", light.translation.x);
|
*writer.text(example_text, 22) = format!("{:.1},", light.translation.x);
|
||||||
example_text.sections[22].value = format!(" {:.1},", light.translation.y);
|
*writer.text(example_text, 23) = format!(" {:.1},", light.translation.y);
|
||||||
example_text.sections[23].value = format!(" {:.1}", light.translation.z);
|
*writer.text(example_text, 24) = format!(" {:.1}", light.translation.z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +221,8 @@ fn adjust_light_position(
|
||||||
fn cycle_filter_methods(
|
fn cycle_filter_methods(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
mut filter_methods: Query<&mut ShadowFilteringMethod>,
|
mut filter_methods: Query<&mut ShadowFilteringMethod>,
|
||||||
mut example_text: Query<&mut Text>,
|
example_text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
if input.just_pressed(KeyCode::KeyF) {
|
if input.just_pressed(KeyCode::KeyF) {
|
||||||
for mut filter_method in &mut filter_methods {
|
for mut filter_method in &mut filter_methods {
|
||||||
|
@ -253,7 +241,7 @@ fn cycle_filter_methods(
|
||||||
ShadowFilteringMethod::Hardware2x2
|
ShadowFilteringMethod::Hardware2x2
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
example_text.single_mut().sections[6].value = filter_method_string;
|
*writer.text(example_text.single(), 7) = filter_method_string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,7 +249,8 @@ fn cycle_filter_methods(
|
||||||
fn adjust_point_light_biases(
|
fn adjust_point_light_biases(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
mut query: Query<&mut PointLight>,
|
mut query: Query<&mut PointLight>,
|
||||||
mut example_text: Query<&mut Text>,
|
example_text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
let depth_bias_step_size = 0.01;
|
let depth_bias_step_size = 0.01;
|
||||||
let normal_bias_step_size = 0.1;
|
let normal_bias_step_size = 0.1;
|
||||||
|
@ -287,15 +276,16 @@ fn adjust_point_light_biases(
|
||||||
light.shadow_normal_bias = 0.0;
|
light.shadow_normal_bias = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
example_text.single_mut().sections[9].value = format!("{:.2}", light.shadow_depth_bias);
|
*writer.text(example_text.single(), 10) = format!("{:.2}", light.shadow_depth_bias);
|
||||||
example_text.single_mut().sections[12].value = format!("{:.1}", light.shadow_normal_bias);
|
*writer.text(example_text.single(), 13) = format!("{:.1}", light.shadow_normal_bias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adjust_directional_light_biases(
|
fn adjust_directional_light_biases(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
mut query: Query<&mut DirectionalLight>,
|
mut query: Query<&mut DirectionalLight>,
|
||||||
mut example_text: Query<&mut Text>,
|
example_text: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
let depth_bias_step_size = 0.01;
|
let depth_bias_step_size = 0.01;
|
||||||
let normal_bias_step_size = 0.1;
|
let normal_bias_step_size = 0.1;
|
||||||
|
@ -321,7 +311,7 @@ fn adjust_directional_light_biases(
|
||||||
light.shadow_normal_bias = 0.0;
|
light.shadow_normal_bias = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
example_text.single_mut().sections[15].value = format!("{:.2}", light.shadow_depth_bias);
|
*writer.text(example_text.single(), 16) = format!("{:.2}", light.shadow_depth_bias);
|
||||||
example_text.single_mut().sections[18].value = format!("{:.1}", light.shadow_normal_bias);
|
*writer.text(example_text.single(), 19) = format!("{:.1}", light.shadow_normal_bias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,16 +95,15 @@ fn setup(
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(
|
parent.spawn((
|
||||||
TextBundle::from_section(*camera_name, TextStyle::default()).with_style(
|
Text::new(*camera_name),
|
||||||
Style {
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.),
|
top: Val::Px(12.),
|
||||||
left: Val::Px(12.),
|
left: Val::Px(12.),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
),
|
));
|
||||||
);
|
|
||||||
buttons_panel(parent);
|
buttons_panel(parent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -150,7 +149,7 @@ fn setup(
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(caption, TextStyle::default()));
|
parent.spawn(Text::new(caption));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,14 +126,15 @@ fn setup(
|
||||||
Transform::from_xyz(-4.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
|
Transform::from_xyz(-4.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(INSTRUCTIONS, TextStyle::default()).with_style(Style {
|
Text::new(INSTRUCTIONS),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn light_sway(time: Res<Time>, mut query: Query<(&mut Transform, &mut SpotLight)>) {
|
fn light_sway(time: Res<Time>, mut query: Query<(&mut Transform, &mut SpotLight)>) {
|
||||||
|
|
|
@ -78,14 +78,15 @@ fn setup(
|
||||||
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, PI * -0.15, PI * -0.15)),
|
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, PI * -0.15, PI * -0.15)),
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
@ -166,7 +167,6 @@ fn update(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut text = text.single_mut();
|
let mut text = text.single_mut();
|
||||||
let text = &mut text.sections[0].value;
|
|
||||||
text.clear();
|
text.clear();
|
||||||
|
|
||||||
let (o, l, m, h, u) = match ssao.map(|s| s.quality_level) {
|
let (o, l, m, h, u) = match ssao.map(|s| s.quality_level) {
|
||||||
|
|
|
@ -251,38 +251,33 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
|
||||||
|
|
||||||
// Spawns the help text.
|
// Spawns the help text.
|
||||||
fn spawn_text(commands: &mut Commands, app_settings: &AppSettings) {
|
fn spawn_text(commands: &mut Commands, app_settings: &AppSettings) {
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
create_text(app_settings),
|
||||||
text: create_text(app_settings),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates or recreates the help text.
|
// Creates or recreates the help text.
|
||||||
fn create_text(app_settings: &AppSettings) -> Text {
|
fn create_text(app_settings: &AppSettings) -> Text {
|
||||||
Text::from_section(
|
format!(
|
||||||
format!(
|
"{}\n{}\n{}",
|
||||||
"{}\n{}\n{}",
|
match app_settings.displayed_model {
|
||||||
match app_settings.displayed_model {
|
DisplayedModel::Cube => SWITCH_TO_FLIGHT_HELMET_HELP_TEXT,
|
||||||
DisplayedModel::Cube => SWITCH_TO_FLIGHT_HELMET_HELP_TEXT,
|
DisplayedModel::FlightHelmet => SWITCH_TO_CUBE_HELP_TEXT,
|
||||||
DisplayedModel::FlightHelmet => SWITCH_TO_CUBE_HELP_TEXT,
|
},
|
||||||
},
|
if app_settings.ssr_on {
|
||||||
if app_settings.ssr_on {
|
TURN_SSR_OFF_HELP_TEXT
|
||||||
TURN_SSR_OFF_HELP_TEXT
|
} else {
|
||||||
} else {
|
TURN_SSR_ON_HELP_TEXT
|
||||||
TURN_SSR_ON_HELP_TEXT
|
},
|
||||||
},
|
MOVE_CAMERA_HELP_TEXT
|
||||||
MOVE_CAMERA_HELP_TEXT
|
|
||||||
),
|
|
||||||
TextStyle::default(),
|
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialExtension for Water {
|
impl MaterialExtension for Water {
|
||||||
|
|
|
@ -81,14 +81,15 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
@ -169,25 +170,22 @@ fn setup_image_viewer_scene(
|
||||||
HDRViewer,
|
HDRViewer,
|
||||||
));
|
));
|
||||||
|
|
||||||
commands
|
commands.spawn((
|
||||||
.spawn((
|
Text::new("Drag and drop an HDR or EXR file"),
|
||||||
TextBundle::from_section(
|
TextStyle {
|
||||||
"Drag and drop an HDR or EXR file",
|
font_size: 36.0,
|
||||||
TextStyle {
|
color: Color::BLACK,
|
||||||
font_size: 36.0,
|
..default()
|
||||||
color: Color::BLACK,
|
},
|
||||||
..default()
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
},
|
Style {
|
||||||
)
|
align_self: AlignSelf::Center,
|
||||||
.with_text_justify(JustifyText::Center)
|
margin: UiRect::all(Val::Auto),
|
||||||
.with_style(Style {
|
..default()
|
||||||
align_self: AlignSelf::Center,
|
},
|
||||||
margin: UiRect::all(Val::Auto),
|
SceneNumber(3),
|
||||||
..default()
|
Visibility::Hidden,
|
||||||
}),
|
));
|
||||||
SceneNumber(3),
|
|
||||||
))
|
|
||||||
.insert(Visibility::Hidden);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -403,13 +401,13 @@ fn update_ui(
|
||||||
*hide_ui = !*hide_ui;
|
*hide_ui = !*hide_ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_text = &text_query.single().sections[0].value;
|
let old_text = text_query.single();
|
||||||
|
|
||||||
if *hide_ui {
|
if *hide_ui {
|
||||||
if !old_text.is_empty() {
|
if !old_text.is_empty() {
|
||||||
// single_mut() always triggers change detection,
|
// single_mut() always triggers change detection,
|
||||||
// so only access if text actually needs changing
|
// so only access if text actually needs changing
|
||||||
text_query.single_mut().sections[0].value.clear();
|
text_query.single_mut().clear();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -534,7 +532,7 @@ fn update_ui(
|
||||||
if text != old_text.as_str() {
|
if text != old_text.as_str() {
|
||||||
// single_mut() always triggers change detection,
|
// single_mut() always triggers change detection,
|
||||||
// so only access if text actually changed
|
// so only access if text actually changed
|
||||||
text_query.single_mut().sections[0].value = text;
|
**text_query.single_mut() = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -331,15 +331,14 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// Controls Text
|
// Controls Text
|
||||||
let text_style = TextStyle::default();
|
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
TextBundle::from_section("", text_style).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
ExampleDisplay,
|
ExampleDisplay,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -549,8 +548,7 @@ fn example_control_system(
|
||||||
Quat::from_euler(EulerRot::XYZ, 0.0, rotation, 0.0),
|
Quat::from_euler(EulerRot::XYZ, 0.0, rotation, 0.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut display = display.single_mut();
|
**display.single_mut() = format!(
|
||||||
display.sections[0].value = format!(
|
|
||||||
concat!(
|
concat!(
|
||||||
" J / K / L / ; Screen Space Specular Transmissive Quality: {:?}\n",
|
" J / K / L / ; Screen Space Specular Transmissive Quality: {:?}\n",
|
||||||
" O / P Screen Space Specular Transmissive Steps: {}\n",
|
" O / P Screen Space Specular Transmissive Steps: {}\n",
|
||||||
|
|
|
@ -149,18 +149,15 @@ fn setup(
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create the text.
|
// Create the text.
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
app_status.create_text(),
|
||||||
text: app_status.create_text(),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to add the `VisibilityRange` components manually, as glTF currently
|
// We need to add the `VisibilityRange` components manually, as glTF currently
|
||||||
|
@ -297,31 +294,29 @@ fn update_help_text(mut text_query: Query<&mut Text>, app_status: Res<AppStatus>
|
||||||
impl AppStatus {
|
impl AppStatus {
|
||||||
// Creates and returns help text reflecting the app status.
|
// Creates and returns help text reflecting the app status.
|
||||||
fn create_text(&self) -> Text {
|
fn create_text(&self) -> Text {
|
||||||
Text::from_section(
|
format!(
|
||||||
format!(
|
"\
|
||||||
"\
|
|
||||||
{} (1) Switch from high-poly to low-poly based on camera distance
|
{} (1) Switch from high-poly to low-poly based on camera distance
|
||||||
{} (2) Show only the high-poly model
|
{} (2) Show only the high-poly model
|
||||||
{} (3) Show only the low-poly model
|
{} (3) Show only the low-poly model
|
||||||
Press 1, 2, or 3 to switch which model is shown
|
Press 1, 2, or 3 to switch which model is shown
|
||||||
Press WASD or use the mouse wheel to move the camera",
|
Press WASD or use the mouse wheel to move the camera",
|
||||||
if self.show_one_model_only.is_none() {
|
if self.show_one_model_only.is_none() {
|
||||||
'>'
|
'>'
|
||||||
} else {
|
} else {
|
||||||
' '
|
' '
|
||||||
},
|
},
|
||||||
if self.show_one_model_only == Some(MainModel::HighPoly) {
|
if self.show_one_model_only == Some(MainModel::HighPoly) {
|
||||||
'>'
|
'>'
|
||||||
} else {
|
} else {
|
||||||
' '
|
' '
|
||||||
},
|
},
|
||||||
if self.show_one_model_only == Some(MainModel::LowPoly) {
|
if self.show_one_model_only == Some(MainModel::LowPoly) {
|
||||||
'>'
|
'>'
|
||||||
} else {
|
} else {
|
||||||
' '
|
' '
|
||||||
},
|
},
|
||||||
),
|
|
||||||
TextStyle::default(),
|
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,38 +123,33 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: R
|
||||||
));
|
));
|
||||||
|
|
||||||
// Add the help text.
|
// Add the help text.
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle {
|
create_text(&app_settings),
|
||||||
text: create_text(&app_settings),
|
Style {
|
||||||
..default()
|
|
||||||
}
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_text(app_settings: &AppSettings) -> Text {
|
fn create_text(app_settings: &AppSettings) -> Text {
|
||||||
Text::from_section(
|
format!(
|
||||||
format!(
|
"{}\n{}\n{}",
|
||||||
"{}\n{}\n{}",
|
"Press WASD or the arrow keys to change the direction of the directional light",
|
||||||
"Press WASD or the arrow keys to change the direction of the directional light",
|
if app_settings.volumetric_pointlight {
|
||||||
if app_settings.volumetric_pointlight {
|
"Press P to turn volumetric point light off"
|
||||||
"Press P to turn volumetric point light off"
|
} else {
|
||||||
} else {
|
"Press P to turn volumetric point light on"
|
||||||
"Press P to turn volumetric point light on"
|
},
|
||||||
},
|
if app_settings.volumetric_spotlight {
|
||||||
if app_settings.volumetric_spotlight {
|
"Press L to turn volumetric spot light off"
|
||||||
"Press L to turn volumetric spot light off"
|
} else {
|
||||||
} else {
|
"Press L to turn volumetric spot light on"
|
||||||
"Press L to turn volumetric spot light on"
|
}
|
||||||
}
|
|
||||||
),
|
|
||||||
TextStyle::default(),
|
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A system that makes directional lights in the glTF scene into volumetric
|
/// A system that makes directional lights in the glTF scene into volumetric
|
||||||
|
|
|
@ -99,14 +99,15 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// Text used to show controls
|
// Text used to show controls
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This system let's you toggle various wireframe settings
|
/// This system let's you toggle various wireframe settings
|
||||||
|
@ -116,7 +117,7 @@ fn update_colors(
|
||||||
mut wireframe_colors: Query<&mut WireframeColor, With<Wireframe>>,
|
mut wireframe_colors: Query<&mut WireframeColor, With<Wireframe>>,
|
||||||
mut text: Query<&mut Text>,
|
mut text: Query<&mut Text>,
|
||||||
) {
|
) {
|
||||||
text.single_mut().sections[0].value = format!(
|
**text.single_mut() = format!(
|
||||||
"Controls
|
"Controls
|
||||||
---------------
|
---------------
|
||||||
Z - Toggle global
|
Z - Toggle global
|
||||||
|
|
|
@ -40,22 +40,22 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnimatableProperty for FontSizeProperty {
|
impl AnimatableProperty for FontSizeProperty {
|
||||||
type Component = Text;
|
type Component = TextStyle;
|
||||||
|
|
||||||
type Property = f32;
|
type Property = f32;
|
||||||
|
|
||||||
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
||||||
Some(&mut component.sections.get_mut(0)?.style.font_size)
|
Some(&mut component.font_size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnimatableProperty for TextColorProperty {
|
impl AnimatableProperty for TextColorProperty {
|
||||||
type Component = Text;
|
type Component = TextStyle;
|
||||||
|
|
||||||
type Property = Srgba;
|
type Property = Srgba;
|
||||||
|
|
||||||
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
||||||
match component.sections.get_mut(0)?.style.color {
|
match component.color {
|
||||||
Color::Srgba(ref mut color) => Some(color),
|
Color::Srgba(ref mut color) => Some(color),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
@ -170,17 +170,16 @@ fn setup(
|
||||||
// Build the text node.
|
// Build the text node.
|
||||||
let player = builder.parent_entity();
|
let player = builder.parent_entity();
|
||||||
builder
|
builder
|
||||||
.spawn(
|
.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Bevy"),
|
||||||
"Bevy",
|
TextStyle {
|
||||||
TextStyle {
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
font_size: 24.0,
|
||||||
font_size: 24.0,
|
color: Color::Srgba(Srgba::RED),
|
||||||
color: Color::Srgba(Srgba::RED),
|
..default()
|
||||||
},
|
},
|
||||||
)
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
.with_text_justify(JustifyText::Center),
|
))
|
||||||
)
|
|
||||||
// Mark as an animation target.
|
// Mark as an animation target.
|
||||||
.insert(AnimationTarget {
|
.insert(AnimationTarget {
|
||||||
id: animation_target_id,
|
id: animation_target_id,
|
||||||
|
|
|
@ -37,11 +37,12 @@ impl AnimationEvent for MessageEvent {
|
||||||
|
|
||||||
fn edit_message(
|
fn edit_message(
|
||||||
mut event_reader: EventReader<MessageEvent>,
|
mut event_reader: EventReader<MessageEvent>,
|
||||||
mut text: Single<&mut Text, With<MessageText>>,
|
text: Single<(&mut Text2d, &mut TextStyle), With<MessageText>>,
|
||||||
) {
|
) {
|
||||||
|
let (mut text, mut style) = text.into_inner();
|
||||||
for event in event_reader.read() {
|
for event in event_reader.read() {
|
||||||
text.sections[0].value = event.value.clone();
|
text.0 = event.value.clone();
|
||||||
text.sections[0].style.color = event.color;
|
style.color = event.color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,16 +68,11 @@ fn setup(
|
||||||
// The text that will be changed by animation events.
|
// The text that will be changed by animation events.
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
MessageText,
|
MessageText,
|
||||||
Text2dBundle {
|
Text2d::default(),
|
||||||
text: Text::from_section(
|
TextStyle {
|
||||||
"",
|
font_size: 119.0,
|
||||||
TextStyle {
|
color: Color::NONE,
|
||||||
font_size: 119.0,
|
..default()
|
||||||
color: Color::NONE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
..Default::default()
|
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -112,10 +108,9 @@ fn setup(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slowly fade out the text opacity.
|
// Slowly fade out the text opacity.
|
||||||
fn animate_text_opacity(mut query: Query<&mut Text>, time: Res<Time>) {
|
fn animate_text_opacity(mut styles: Query<&mut TextStyle>, time: Res<Time>) {
|
||||||
for mut text in &mut query {
|
for mut style in &mut styles {
|
||||||
let color = &mut text.sections[0].style.color;
|
let a = style.color.alpha();
|
||||||
let a = color.alpha();
|
style.color.set_alpha(a - time.delta_seconds());
|
||||||
color.set_alpha(a - time.delta_seconds());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,16 +250,15 @@ fn setup_scene(
|
||||||
|
|
||||||
/// Places the help text at the top left of the window.
|
/// Places the help text at the top left of the window.
|
||||||
fn setup_help_text(commands: &mut Commands) {
|
fn setup_help_text(commands: &mut Commands) {
|
||||||
commands.spawn(TextBundle {
|
commands.spawn((
|
||||||
text: Text::from_section(HELP_TEXT, TextStyle::default()),
|
Text::new(HELP_TEXT),
|
||||||
style: Style {
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the node UI widgets.
|
/// Initializes the node UI widgets.
|
||||||
|
@ -271,18 +270,15 @@ fn setup_node_rects(commands: &mut Commands) {
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = commands
|
let text = commands
|
||||||
.spawn(TextBundle {
|
.spawn((
|
||||||
text: Text::from_section(
|
Text::new(node_string),
|
||||||
node_string,
|
TextStyle {
|
||||||
TextStyle {
|
font_size: 16.0,
|
||||||
font_size: 16.0,
|
color: ANTIQUE_WHITE.into(),
|
||||||
color: ANTIQUE_WHITE.into(),
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
)
|
))
|
||||||
.with_justify(JustifyText::Center),
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.id();
|
.id();
|
||||||
|
|
||||||
let container = {
|
let container = {
|
||||||
|
@ -444,7 +440,7 @@ fn update_ui(
|
||||||
// Update the node labels with the current weights.
|
// Update the node labels with the current weights.
|
||||||
let mut text_iter = text_query.iter_many_mut(children);
|
let mut text_iter = text_query.iter_many_mut(children);
|
||||||
if let Some(mut text) = text_iter.fetch_next() {
|
if let Some(mut text) = text_iter.fetch_next() {
|
||||||
text.sections[0].value = format!(
|
**text = format!(
|
||||||
"{}\n{:.2}",
|
"{}\n{:.2}",
|
||||||
clip_node.text, animation_weights.weights[clip_node.index]
|
clip_node.text, animation_weights.weights[clip_node.index]
|
||||||
);
|
);
|
||||||
|
|
|
@ -156,18 +156,15 @@ fn setup_scene(
|
||||||
// Creates the UI.
|
// Creates the UI.
|
||||||
fn setup_ui(mut commands: Commands) {
|
fn setup_ui(mut commands: Commands) {
|
||||||
// Add help text.
|
// Add help text.
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Click on a button to toggle animations for its associated bones"),
|
||||||
"Click on a button to toggle animations for its associated bones",
|
Style {
|
||||||
TextStyle::default(),
|
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
// Add the buttons that allow the user to toggle mask groups on and off.
|
// Add the buttons that allow the user to toggle mask groups on and off.
|
||||||
commands
|
commands
|
||||||
|
@ -286,14 +283,14 @@ fn add_mask_group_control(parent: &mut ChildBuilder, label: &str, width: Val, ma
|
||||||
background_color: Color::BLACK.into(),
|
background_color: Color::BLACK.into(),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_child(TextBundle {
|
.with_child((
|
||||||
text: Text::from_section(label, label_text_style.clone()),
|
Text::new(label),
|
||||||
style: Style {
|
label_text_style.clone(),
|
||||||
|
Style {
|
||||||
margin: UiRect::vertical(Val::Px(3.0)),
|
margin: UiRect::vertical(Val::Px(3.0)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.spawn(NodeBundle {
|
.spawn(NodeBundle {
|
||||||
|
@ -337,29 +334,24 @@ fn add_mask_group_control(parent: &mut ChildBuilder, label: &str, width: Val, ma
|
||||||
border_color: BorderColor(Color::WHITE),
|
border_color: BorderColor(Color::WHITE),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_child(
|
.with_child((
|
||||||
TextBundle {
|
Text(format!("{:?}", label)),
|
||||||
style: Style {
|
if index > 0 {
|
||||||
flex_grow: 1.0,
|
button_text_style.clone()
|
||||||
margin: UiRect::vertical(Val::Px(3.0)),
|
} else {
|
||||||
..default()
|
selected_button_text_style.clone()
|
||||||
},
|
},
|
||||||
text: Text::from_section(
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
format!("{:?}", label),
|
Style {
|
||||||
if index > 0 {
|
flex_grow: 1.0,
|
||||||
button_text_style.clone()
|
margin: UiRect::vertical(Val::Px(3.0)),
|
||||||
} else {
|
|
||||||
selected_button_text_style.clone()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
..default()
|
..default()
|
||||||
}
|
},
|
||||||
.with_text_justify(JustifyText::Center),
|
AnimationControl {
|
||||||
)
|
group_id: mask_group_id,
|
||||||
.insert(AnimationControl {
|
label: *label,
|
||||||
group_id: mask_group_id,
|
},
|
||||||
label: *label,
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -482,7 +474,8 @@ fn handle_button_toggles(
|
||||||
// A system that updates the UI based on the current app state.
|
// A system that updates the UI based on the current app state.
|
||||||
fn update_ui(
|
fn update_ui(
|
||||||
mut animation_controls: Query<(&AnimationControl, &mut BackgroundColor, &Children)>,
|
mut animation_controls: Query<(&AnimationControl, &mut BackgroundColor, &Children)>,
|
||||||
mut texts: Query<&mut Text>,
|
texts: Query<Entity, With<Text>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
app_state: Res<AppState>,
|
app_state: Res<AppState>,
|
||||||
) {
|
) {
|
||||||
for (animation_control, mut background_color, kids) in animation_controls.iter_mut() {
|
for (animation_control, mut background_color, kids) in animation_controls.iter_mut() {
|
||||||
|
@ -496,13 +489,13 @@ fn update_ui(
|
||||||
};
|
};
|
||||||
|
|
||||||
for &kid in kids {
|
for &kid in kids {
|
||||||
let Ok(mut text) = texts.get_mut(kid) else {
|
let Ok(text) = texts.get(kid) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
for section in &mut text.sections {
|
writer.for_each_style(text, |mut style| {
|
||||||
section.style.color = if enabled { Color::BLACK } else { Color::WHITE };
|
style.color = if enabled { Color::BLACK } else { Color::WHITE };
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,22 +63,17 @@ fn setup(mut commands: Commands) {
|
||||||
let color = Hsla::hsl(i as f32 / 11.0 * 360.0, 0.8, 0.75).into();
|
let color = Hsla::hsl(i as f32 / 11.0 * 360.0, 0.8, 0.75).into();
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Text2dBundle {
|
Text2d(format!("{:?}", function)),
|
||||||
text: Text::from_section(
|
TextStyle {
|
||||||
format!("{:?}", function),
|
color,
|
||||||
TextStyle {
|
..text_style.clone()
|
||||||
color,
|
|
||||||
..text_style.clone()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
transform: Transform::from_xyz(
|
|
||||||
i as f32 * 113.0 - 1280.0 / 2.0 + 25.0,
|
|
||||||
-100.0 - ((j as f32 * 250.0) - 300.0),
|
|
||||||
0.0,
|
|
||||||
),
|
|
||||||
text_anchor: Anchor::TopLeft,
|
|
||||||
..default()
|
|
||||||
},
|
},
|
||||||
|
Transform::from_xyz(
|
||||||
|
i as f32 * 113.0 - 1280.0 / 2.0 + 25.0,
|
||||||
|
-100.0 - ((j as f32 * 250.0) - 300.0),
|
||||||
|
0.0,
|
||||||
|
),
|
||||||
|
Anchor::TopLeft,
|
||||||
SelectedEaseFunction(*function, color),
|
SelectedEaseFunction(*function, color),
|
||||||
))
|
))
|
||||||
.with_children(|p| {
|
.with_children(|p| {
|
||||||
|
@ -93,14 +88,15 @@ fn setup(mut commands: Commands) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("", TextStyle::default()).with_style(Style {
|
Text::default(),
|
||||||
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const SIZE_PER_FUNCTION: f32 = 95.0;
|
const SIZE_PER_FUNCTION: f32 = 95.0;
|
||||||
|
@ -109,7 +105,7 @@ fn display_curves(
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
ease_functions: Query<(&SelectedEaseFunction, &Transform, &Children)>,
|
ease_functions: Query<(&SelectedEaseFunction, &Transform, &Children)>,
|
||||||
mut transforms: Query<&mut Transform, Without<SelectedEaseFunction>>,
|
mut transforms: Query<&mut Transform, Without<SelectedEaseFunction>>,
|
||||||
mut ui: Query<&mut Text, With<Node>>,
|
mut ui_text: Single<&mut Text>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
let samples = 100;
|
let samples = 100;
|
||||||
|
@ -119,7 +115,7 @@ fn display_curves(
|
||||||
let now = ((time.elapsed_seconds() % (duration + time_margin * 2.0) - time_margin) / duration)
|
let now = ((time.elapsed_seconds() % (duration + time_margin * 2.0) - time_margin) / duration)
|
||||||
.clamp(0.0, 1.0);
|
.clamp(0.0, 1.0);
|
||||||
|
|
||||||
ui.single_mut().sections[0].value = format!("Progress: {:.2}", now);
|
ui_text.0 = format!("Progress: {:.2}", now);
|
||||||
|
|
||||||
for (SelectedEaseFunction(function, color), transform, children) in &ease_functions {
|
for (SelectedEaseFunction(function, color), transform, children) in &ease_functions {
|
||||||
// Draw a box around the curve
|
// Draw a box around the curve
|
||||||
|
|
|
@ -143,10 +143,7 @@ fn print_logs(
|
||||||
|
|
||||||
commands.entity(root_entity).with_children(|child| {
|
commands.entity(root_entity).with_children(|child| {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
child.spawn(TextBundle::from_section(
|
child.spawn(Text::new(&event.message));
|
||||||
&event.message,
|
|
||||||
TextStyle::default(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,16 +19,15 @@ fn main() {
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
fn setup(mut commands: Commands) {
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
commands.spawn(TextBundle {
|
commands.spawn((
|
||||||
text: Text::from_section("Press P to panic", TextStyle::default()),
|
Text::new("Press P to panic"),
|
||||||
style: Style {
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
..default()
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn panic_on_p(keys: Res<ButtonInput<KeyCode>>) {
|
fn panic_on_p(keys: Res<ButtonInput<KeyCode>>) {
|
||||||
|
|
|
@ -147,13 +147,9 @@ fn spawn_text(mut commands: Commands) {
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn(Text::new("Space: swap meshes by mutating a Handle<Mesh>"));
|
||||||
"Space: swap meshes by mutating a Handle<Mesh>",
|
parent.spawn(Text::new(
|
||||||
TextStyle::default(),
|
|
||||||
));
|
|
||||||
parent.spawn(TextBundle::from_section(
|
|
||||||
"Return: mutate the mesh itself, changing all copies of it",
|
"Return: mutate the mesh itself, changing all copies of it",
|
||||||
TextStyle::default(),
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,13 +105,11 @@ fn spawn_text(mut commands: Commands) {
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn(Text::new(
|
||||||
"Space: swap image texture paths by mutating a Handle<Image>",
|
"Space: swap image texture paths by mutating a Handle<Image>",
|
||||||
TextStyle::default(),
|
|
||||||
));
|
));
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn(Text::new(
|
||||||
"Return: mutate the image Asset itself, changing all copies of it",
|
"Return: mutate the image Asset itself, changing all copies of it",
|
||||||
TextStyle::default(),
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,21 +181,13 @@ fn setup_ui(mut commands: Commands) {
|
||||||
})
|
})
|
||||||
.with_children(|b| {
|
.with_children(|b| {
|
||||||
b.spawn((
|
b.spawn((
|
||||||
TextBundle {
|
Text::new("Loading...".to_owned()),
|
||||||
text: Text {
|
TextStyle {
|
||||||
sections: vec![TextSection {
|
font_size: 53.0,
|
||||||
value: "Loading...".to_owned(),
|
color: Color::BLACK,
|
||||||
style: TextStyle {
|
|
||||||
font_size: 53.0,
|
|
||||||
color: Color::BLACK,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
justify: JustifyText::Right,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
TextBlock::new_with_justify(JustifyText::Right),
|
||||||
LoadingText,
|
LoadingText,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
@ -278,7 +270,7 @@ fn get_async_loading_state(
|
||||||
if is_loaded {
|
if is_loaded {
|
||||||
next_loading_state.set(LoadingState::Loaded);
|
next_loading_state.set(LoadingState::Loaded);
|
||||||
if let Ok(mut text) = text.get_single_mut() {
|
if let Ok(mut text) = text.get_single_mut() {
|
||||||
"Loaded!".clone_into(&mut text.sections[0].value);
|
"Loaded!".clone_into(&mut **text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,18 +57,18 @@ fn spawn_text(mut commands: Commands, mut reader: EventReader<StreamEvent>) {
|
||||||
let text_style = TextStyle::default();
|
let text_style = TextStyle::default();
|
||||||
|
|
||||||
for (per_frame, event) in reader.read().enumerate() {
|
for (per_frame, event) in reader.read().enumerate() {
|
||||||
commands.spawn(Text2dBundle {
|
commands.spawn((
|
||||||
text: Text::from_section(event.0.to_string(), text_style.clone())
|
Text2d::new(event.0.to_string()),
|
||||||
.with_justify(JustifyText::Center),
|
text_style.clone(),
|
||||||
transform: Transform::from_xyz(per_frame as f32 * 100.0, 300.0, 0.0),
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
..default()
|
Transform::from_xyz(per_frame as f32 * 100.0, 300.0, 0.0),
|
||||||
});
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_text(
|
fn move_text(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut texts: Query<(Entity, &mut Transform), With<Text>>,
|
mut texts: Query<(Entity, &mut Transform), With<Text2d>>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
for (entity, mut position) in &mut texts {
|
for (entity, mut position) in &mut texts {
|
||||||
|
|
|
@ -59,18 +59,15 @@ fn setup(
|
||||||
});
|
});
|
||||||
|
|
||||||
// example instructions
|
// example instructions
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Up/Down/Left/Right: Move Listener\nSpace: Toggle Emitter Movement"),
|
||||||
"Up/Down/Left/Right: Move Listener\nSpace: Toggle Emitter Movement",
|
Style {
|
||||||
TextStyle::default(),
|
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
// camera
|
// camera
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
|
|
|
@ -58,18 +58,15 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// example instructions
|
// example instructions
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Up/Down/Left/Right: Move Listener\nSpace: Toggle Emitter Movement"),
|
||||||
"Up/Down/Left/Right: Move Listener\nSpace: Toggle Emitter Movement",
|
Style {
|
||||||
TextStyle::default(),
|
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
// camera
|
// camera
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
|
|
|
@ -63,15 +63,15 @@ fn setup_scene(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_instructions(mut commands: Commands) {
|
fn setup_instructions(mut commands: Commands) {
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section("Hold space to trigger a screen shake", TextStyle::default())
|
Text::new("Hold space to trigger a screen shake"),
|
||||||
.with_style(Style {
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_camera(mut commands: Commands) {
|
fn setup_camera(mut commands: Commands) {
|
||||||
|
|
|
@ -49,18 +49,15 @@ fn setup_scene(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_instructions(mut commands: Commands) {
|
fn setup_instructions(mut commands: Commands) {
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Move the light with WASD.\nThe camera will smoothly track the light."),
|
||||||
"Move the light with WASD.\nThe camera will smoothly track the light.",
|
Style {
|
||||||
TextStyle::default(),
|
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
bottom: Val::Px(12.0),
|
bottom: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_camera(mut commands: Commands) {
|
fn setup_camera(mut commands: Commands) {
|
||||||
|
|
|
@ -95,18 +95,9 @@ fn instructions(mut commands: Commands) {
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn(Text::new("Mouse up or down: pitch"));
|
||||||
"Mouse up or down: pitch",
|
parent.spawn(Text::new("Mouse left or right: yaw"));
|
||||||
TextStyle::default(),
|
parent.spawn(Text::new("Mouse buttons: roll"));
|
||||||
));
|
|
||||||
parent.spawn(TextBundle::from_section(
|
|
||||||
"Mouse left or right: yaw",
|
|
||||||
TextStyle::default(),
|
|
||||||
));
|
|
||||||
parent.spawn(TextBundle::from_section(
|
|
||||||
"Mouse buttons: roll",
|
|
||||||
TextStyle::default(),
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,16 +203,11 @@ fn spawn_text(mut commands: Commands) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_child(Text::new(concat!(
|
||||||
parent.spawn(TextBundle::from_section(
|
"Move the camera with your mouse.\n",
|
||||||
concat!(
|
"Press arrow up to decrease the FOV of the world model.\n",
|
||||||
"Move the camera with your mouse.\n",
|
"Press arrow down to increase the FOV of the world model."
|
||||||
"Press arrow up to decrease the FOV of the world model.\n",
|
)));
|
||||||
"Press arrow down to increase the FOV of the world model."
|
|
||||||
),
|
|
||||||
TextStyle::default(),
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_player(
|
fn move_player(
|
||||||
|
|
|
@ -104,13 +104,9 @@ fn instructions(mut commands: Commands) {
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn(Text::new("Scroll mouse wheel to zoom in/out"));
|
||||||
"Scroll mouse wheel to zoom in/out",
|
parent.spawn(Text::new(
|
||||||
TextStyle::default(),
|
|
||||||
));
|
|
||||||
parent.spawn(TextBundle::from_section(
|
|
||||||
"Space: switch between orthographic and perspective projections",
|
"Space: switch between orthographic and perspective projections",
|
||||||
TextStyle::default(),
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use bevy::{
|
use bevy::{
|
||||||
dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin},
|
dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
text::FontSmoothing,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OverlayColor;
|
struct OverlayColor;
|
||||||
|
@ -25,6 +26,8 @@ fn main() {
|
||||||
color: OverlayColor::GREEN,
|
color: OverlayColor::GREEN,
|
||||||
// If we want, we can use a custom font
|
// If we want, we can use a custom font
|
||||||
font: default(),
|
font: default(),
|
||||||
|
// We could also disable font smoothing,
|
||||||
|
font_smoothing: FontSmoothing::default(),
|
||||||
},
|
},
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
@ -52,15 +55,12 @@ fn setup(mut commands: Commands) {
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|c| {
|
.with_children(|c| {
|
||||||
c.spawn(TextBundle::from_section(
|
c.spawn(Text::new(concat!(
|
||||||
concat!(
|
"Press 1 to toggle the overlay color.\n",
|
||||||
"Press 1 to toggle the overlay color.\n",
|
"Press 2 to decrease the overlay size.\n",
|
||||||
"Press 2 to decrease the overlay size.\n",
|
"Press 3 to increase the overlay size.\n",
|
||||||
"Press 3 to increase the overlay size.\n",
|
"Press 4 to toggle the overlay visibility."
|
||||||
"Press 4 to toggle the overlay visibility."
|
)));
|
||||||
),
|
|
||||||
TextStyle::default(),
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,22 +71,22 @@ struct Explode;
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
fn setup(mut commands: Commands) {
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new(
|
||||||
"Click on a \"Mine\" to trigger it.\n\
|
"Click on a \"Mine\" to trigger it.\n\
|
||||||
When it explodes it will trigger all overlapping mines.",
|
When it explodes it will trigger all overlapping mines.",
|
||||||
TextStyle {
|
),
|
||||||
color: Color::WHITE,
|
TextStyle {
|
||||||
..default()
|
color: Color::WHITE,
|
||||||
},
|
..default()
|
||||||
)
|
},
|
||||||
.with_style(Style {
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.),
|
top: Val::Px(12.),
|
||||||
left: Val::Px(12.),
|
left: Val::Px(12.),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
|
let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
|
||||||
|
|
||||||
|
|
|
@ -79,40 +79,37 @@ fn evaluate_callbacks(query: Query<(Entity, &Callback), With<Triggered>>, mut co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn system_a(mut query: Query<&mut Text>) {
|
fn system_a(query: Query<Entity, With<Text>>, mut writer: UiTextWriter) {
|
||||||
let mut text = query.single_mut();
|
*writer.text(query.single(), 3) = String::from("A");
|
||||||
text.sections[2].value = String::from("A");
|
|
||||||
info!("A: One shot system registered with Commands was triggered");
|
info!("A: One shot system registered with Commands was triggered");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn system_b(mut query: Query<&mut Text>) {
|
fn system_b(query: Query<Entity, With<Text>>, mut writer: UiTextWriter) {
|
||||||
let mut text = query.single_mut();
|
*writer.text(query.single(), 3) = String::from("B");
|
||||||
text.sections[2].value = String::from("B");
|
|
||||||
info!("B: One shot system registered with World was triggered");
|
info!("B: One shot system registered with World was triggered");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_ui(mut commands: Commands) {
|
fn setup_ui(mut commands: Commands) {
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
commands.spawn(
|
commands
|
||||||
TextBundle::from_sections([
|
.spawn((
|
||||||
TextSection::new(
|
Text::default(),
|
||||||
"Press A or B to trigger a one-shot system\n",
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
TextStyle::default(),
|
Style {
|
||||||
),
|
align_self: AlignSelf::Center,
|
||||||
TextSection::new("Last Triggered: ", TextStyle::default()),
|
justify_self: JustifySelf::Center,
|
||||||
TextSection::new(
|
..default()
|
||||||
"-",
|
},
|
||||||
|
))
|
||||||
|
.with_children(|p| {
|
||||||
|
p.spawn(TextSpan::new("Press A or B to trigger a one-shot system\n"));
|
||||||
|
p.spawn(TextSpan::new("Last Triggered: "));
|
||||||
|
p.spawn((
|
||||||
|
TextSpan::new("-"),
|
||||||
TextStyle {
|
TextStyle {
|
||||||
color: bevy::color::palettes::css::ORANGE.into(),
|
color: bevy::color::palettes::css::ORANGE.into(),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
),
|
));
|
||||||
])
|
});
|
||||||
.with_text_justify(JustifyText::Center)
|
|
||||||
.with_style(Style {
|
|
||||||
align_self: AlignSelf::Center,
|
|
||||||
justify_self: JustifySelf::Center,
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,22 +175,20 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
|
||||||
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/cakeBirthday.glb"));
|
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/cakeBirthday.glb"));
|
||||||
|
|
||||||
// scoreboard
|
// scoreboard
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Score:"),
|
||||||
"Score:",
|
TextStyle {
|
||||||
TextStyle {
|
font_size: 33.0,
|
||||||
font_size: 33.0,
|
color: Color::srgb(0.5, 0.5, 1.0),
|
||||||
color: Color::srgb(0.5, 0.5, 1.0),
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
Style {
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(5.0),
|
top: Val::Px(5.0),
|
||||||
left: Val::Px(5.0),
|
left: Val::Px(5.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
commands.insert_resource(Random(rng));
|
commands.insert_resource(Random(rng));
|
||||||
}
|
}
|
||||||
|
@ -380,8 +378,7 @@ fn rotate_bonus(game: Res<Game>, time: Res<Time>, mut transforms: Query<&mut Tra
|
||||||
|
|
||||||
// update the score displayed during the game
|
// update the score displayed during the game
|
||||||
fn scoreboard_system(game: Res<Game>, mut query: Query<&mut Text>) {
|
fn scoreboard_system(game: Res<Game>, mut query: Query<&mut Text>) {
|
||||||
let mut text = query.single_mut();
|
**query.single_mut() = format!("Sugar Rush: {}", game.score);
|
||||||
text.sections[0].value = format!("Sugar Rush: {}", game.score);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// restart the game when pressing spacebar
|
// restart the game when pressing spacebar
|
||||||
|
@ -406,14 +403,12 @@ fn display_score(mut commands: Commands, game: Res<Game>) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_child((
|
||||||
parent.spawn(TextBundle::from_section(
|
Text::new(format!("Cake eaten: {}", game.cake_eaten)),
|
||||||
format!("Cake eaten: {}", game.cake_eaten),
|
TextStyle {
|
||||||
TextStyle {
|
font_size: 67.0,
|
||||||
font_size: 67.0,
|
color: Color::srgb(0.5, 0.5, 1.0),
|
||||||
color: Color::srgb(0.5, 0.5, 1.0),
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
));
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,30 +216,30 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// Scoreboard
|
// Scoreboard
|
||||||
commands.spawn((
|
commands
|
||||||
ScoreboardUi,
|
.spawn((
|
||||||
TextBundle::from_sections([
|
Text::new("Score: "),
|
||||||
TextSection::new(
|
TextStyle {
|
||||||
"Score: ",
|
font_size: SCOREBOARD_FONT_SIZE,
|
||||||
TextStyle {
|
color: TEXT_COLOR,
|
||||||
font_size: SCOREBOARD_FONT_SIZE,
|
..default()
|
||||||
color: TEXT_COLOR,
|
},
|
||||||
..default()
|
ScoreboardUi,
|
||||||
},
|
Style {
|
||||||
),
|
position_type: PositionType::Absolute,
|
||||||
TextSection::from_style(TextStyle {
|
top: SCOREBOARD_TEXT_PADDING,
|
||||||
|
left: SCOREBOARD_TEXT_PADDING,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_child((
|
||||||
|
TextSpan::default(),
|
||||||
|
TextStyle {
|
||||||
font_size: SCOREBOARD_FONT_SIZE,
|
font_size: SCOREBOARD_FONT_SIZE,
|
||||||
color: SCORE_COLOR,
|
color: SCORE_COLOR,
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
])
|
));
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
top: SCOREBOARD_TEXT_PADDING,
|
|
||||||
left: SCOREBOARD_TEXT_PADDING,
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Walls
|
// Walls
|
||||||
commands.spawn(WallBundle::new(WallLocation::Left));
|
commands.spawn(WallBundle::new(WallLocation::Left));
|
||||||
|
@ -334,9 +334,12 @@ fn apply_velocity(mut query: Query<(&mut Transform, &Velocity)>, time: Res<Time>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_scoreboard(score: Res<Score>, mut query: Query<&mut Text, With<ScoreboardUi>>) {
|
fn update_scoreboard(
|
||||||
let mut text = query.single_mut();
|
score: Res<Score>,
|
||||||
text.sections[1].value = score.to_string();
|
query: Query<Entity, (With<ScoreboardUi>, With<Text>)>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
|
) {
|
||||||
|
*writer.text(query.single(), 1) = score.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_for_collisions(
|
fn check_for_collisions(
|
||||||
|
|
|
@ -134,30 +134,34 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
|
|
||||||
commands.spawn((
|
commands
|
||||||
TextBundle::from_sections([
|
.spawn((
|
||||||
TextSection::new("Contributor showcase", text_style.clone()),
|
Text::new("Contributor showcase"),
|
||||||
TextSection::from_style(TextStyle {
|
text_style.clone(),
|
||||||
|
ContributorDisplay,
|
||||||
|
Style {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
top: Val::Px(12.),
|
||||||
|
left: Val::Px(12.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_child((
|
||||||
|
TextSpan::default(),
|
||||||
|
TextStyle {
|
||||||
font_size: 30.,
|
font_size: 30.,
|
||||||
..text_style
|
..text_style
|
||||||
}),
|
},
|
||||||
])
|
));
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
top: Val::Px(12.),
|
|
||||||
left: Val::Px(12.),
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
ContributorDisplay,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the next contributor to display and selects the entity
|
/// Finds the next contributor to display and selects the entity
|
||||||
fn selection(
|
fn selection(
|
||||||
mut timer: ResMut<SelectionTimer>,
|
mut timer: ResMut<SelectionTimer>,
|
||||||
mut contributor_selection: ResMut<ContributorSelection>,
|
mut contributor_selection: ResMut<ContributorSelection>,
|
||||||
mut text_query: Query<&mut Text, With<ContributorDisplay>>,
|
text_query: Query<Entity, (With<ContributorDisplay>, With<Text>)>,
|
||||||
mut query: Query<(&Contributor, &mut Sprite, &mut Transform)>,
|
mut query: Query<(&Contributor, &mut Sprite, &mut Transform)>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
if !timer.0.tick(time.delta()).just_finished() {
|
if !timer.0.tick(time.delta()).just_finished() {
|
||||||
|
@ -182,8 +186,14 @@ fn selection(
|
||||||
let entity = contributor_selection.order[contributor_selection.idx];
|
let entity = contributor_selection.order[contributor_selection.idx];
|
||||||
|
|
||||||
if let Ok((contributor, mut sprite, mut transform)) = query.get_mut(entity) {
|
if let Ok((contributor, mut sprite, mut transform)) = query.get_mut(entity) {
|
||||||
let mut text = text_query.single_mut();
|
let entity = text_query.single();
|
||||||
select(&mut sprite, contributor, &mut transform, &mut text);
|
select(
|
||||||
|
&mut sprite,
|
||||||
|
contributor,
|
||||||
|
&mut transform,
|
||||||
|
entity,
|
||||||
|
&mut writer,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,19 +203,20 @@ fn select(
|
||||||
sprite: &mut Sprite,
|
sprite: &mut Sprite,
|
||||||
contributor: &Contributor,
|
contributor: &Contributor,
|
||||||
transform: &mut Transform,
|
transform: &mut Transform,
|
||||||
text: &mut Text,
|
entity: Entity,
|
||||||
|
writer: &mut UiTextWriter,
|
||||||
) {
|
) {
|
||||||
sprite.color = SELECTED.with_hue(contributor.hue).into();
|
sprite.color = SELECTED.with_hue(contributor.hue).into();
|
||||||
|
|
||||||
transform.translation.z = 100.0;
|
transform.translation.z = 100.0;
|
||||||
|
|
||||||
text.sections[0].value.clone_from(&contributor.name);
|
writer.text(entity, 0).clone_from(&contributor.name);
|
||||||
text.sections[1].value = format!(
|
*writer.text(entity, 1) = format!(
|
||||||
"\n{} commit{}",
|
"\n{} commit{}",
|
||||||
contributor.num_commits,
|
contributor.num_commits,
|
||||||
if contributor.num_commits > 1 { "s" } else { "" }
|
if contributor.num_commits > 1 { "s" } else { "" }
|
||||||
);
|
);
|
||||||
text.sections[0].style.color = sprite.color;
|
writer.style(entity, 0).color = sprite.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the tint color to the "deselected" color and push
|
/// Change the tint color to the "deselected" color and push
|
||||||
|
|
|
@ -113,14 +113,9 @@ fn setup(
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Text2dBundle {
|
Text2d::new("Press Space to play on your desktop! Press it again to return.\nRight click Bevy logo to exit."),
|
||||||
text: Text::from_section(
|
text_style.clone(),
|
||||||
"Press Space to play on your desktop! Press it again to return.\nRight click Bevy logo to exit.",
|
Transform::from_xyz(0.0, -300.0, 100.0),
|
||||||
text_style.clone(),
|
|
||||||
),
|
|
||||||
transform: Transform::from_xyz(0.0, -300.0, 100.0),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
InstructionsText,
|
InstructionsText,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -173,54 +173,52 @@ mod game {
|
||||||
background_color: Color::BLACK.into(),
|
background_color: Color::BLACK.into(),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_children(|p| {
|
||||||
// Display two lines of text, the second one with the current settings
|
p.spawn((
|
||||||
parent.spawn(
|
Text::new("Will be back to the menu shortly..."),
|
||||||
TextBundle::from_section(
|
TextStyle {
|
||||||
"Will be back to the menu shortly...",
|
font_size: 67.0,
|
||||||
|
color: TEXT_COLOR,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Style {
|
||||||
|
margin: UiRect::all(Val::Px(50.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
p.spawn((
|
||||||
|
Text::default(),
|
||||||
|
Style {
|
||||||
|
margin: UiRect::all(Val::Px(50.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_children(|p| {
|
||||||
|
p.spawn((
|
||||||
|
TextSpan(format!("quality: {:?}", *display_quality)),
|
||||||
TextStyle {
|
TextStyle {
|
||||||
font_size: 67.0,
|
font_size: 50.0,
|
||||||
|
color: BLUE.into(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
p.spawn((
|
||||||
|
TextSpan::new(" - "),
|
||||||
|
TextStyle {
|
||||||
|
font_size: 50.0,
|
||||||
color: TEXT_COLOR,
|
color: TEXT_COLOR,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
)
|
));
|
||||||
.with_style(Style {
|
p.spawn((
|
||||||
margin: UiRect::all(Val::Px(50.0)),
|
TextSpan(format!("volume: {:?}", *volume)),
|
||||||
..default()
|
TextStyle {
|
||||||
}),
|
font_size: 50.0,
|
||||||
);
|
color: LIME.into(),
|
||||||
parent.spawn(
|
..default()
|
||||||
TextBundle::from_sections([
|
},
|
||||||
TextSection::new(
|
));
|
||||||
format!("quality: {:?}", *display_quality),
|
});
|
||||||
TextStyle {
|
|
||||||
font_size: 50.0,
|
|
||||||
color: BLUE.into(),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextSection::new(
|
|
||||||
" - ",
|
|
||||||
TextStyle {
|
|
||||||
font_size: 50.0,
|
|
||||||
color: TEXT_COLOR,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextSection::new(
|
|
||||||
format!("volume: {:?}", *volume),
|
|
||||||
TextStyle {
|
|
||||||
font_size: 50.0,
|
|
||||||
color: LIME.into(),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.with_style(Style {
|
|
||||||
margin: UiRect::all(Val::Px(50.0)),
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Spawn a 5 seconds timer to trigger going back to the menu
|
// Spawn a 5 seconds timer to trigger going back to the menu
|
||||||
|
@ -433,20 +431,18 @@ mod menu {
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
// Display the game name
|
// Display the game name
|
||||||
parent.spawn(
|
parent.spawn((
|
||||||
TextBundle::from_section(
|
Text::new("Bevy Game Menu UI"),
|
||||||
"Bevy Game Menu UI",
|
TextStyle {
|
||||||
TextStyle {
|
font_size: 67.0,
|
||||||
font_size: 67.0,
|
color: TEXT_COLOR,
|
||||||
color: TEXT_COLOR,
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
Style {
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
margin: UiRect::all(Val::Px(50.0)),
|
margin: UiRect::all(Val::Px(50.0)),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
// Display three buttons for each action available from the main menu:
|
// Display three buttons for each action available from the main menu:
|
||||||
// - new game
|
// - new game
|
||||||
|
@ -468,10 +464,7 @@ mod menu {
|
||||||
image: UiImage::new(icon),
|
image: UiImage::new(icon),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn((Text::new("New Game"), button_text_style.clone()));
|
||||||
"New Game",
|
|
||||||
button_text_style.clone(),
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
parent
|
parent
|
||||||
.spawn((
|
.spawn((
|
||||||
|
@ -489,10 +482,7 @@ mod menu {
|
||||||
image: UiImage::new(icon),
|
image: UiImage::new(icon),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn((Text::new("Settings"), button_text_style.clone()));
|
||||||
"Settings",
|
|
||||||
button_text_style.clone(),
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
parent
|
parent
|
||||||
.spawn((
|
.spawn((
|
||||||
|
@ -510,7 +500,7 @@ mod menu {
|
||||||
image: UiImage::new(icon),
|
image: UiImage::new(icon),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
parent.spawn(TextBundle::from_section("Quit", button_text_style));
|
parent.spawn((Text::new("Quit"), button_text_style));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -573,10 +563,7 @@ mod menu {
|
||||||
action,
|
action,
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn((Text::new(text), button_text_style.clone()));
|
||||||
text,
|
|
||||||
button_text_style.clone(),
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -637,8 +624,8 @@ mod menu {
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
// Display a label for the current setting
|
// Display a label for the current setting
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn((
|
||||||
"Display Quality",
|
Text::new("Display Quality"),
|
||||||
button_text_style.clone(),
|
button_text_style.clone(),
|
||||||
));
|
));
|
||||||
// Display a button for each possible value
|
// Display a button for each possible value
|
||||||
|
@ -660,8 +647,8 @@ mod menu {
|
||||||
quality_setting,
|
quality_setting,
|
||||||
));
|
));
|
||||||
entity.with_children(|parent| {
|
entity.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn((
|
||||||
format!("{quality_setting:?}"),
|
Text::new(format!("{quality_setting:?}")),
|
||||||
button_text_style.clone(),
|
button_text_style.clone(),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
@ -681,7 +668,7 @@ mod menu {
|
||||||
MenuButtonAction::BackToSettings,
|
MenuButtonAction::BackToSettings,
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section("Back", button_text_style));
|
parent.spawn((Text::new("Back"), button_text_style));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -738,10 +725,7 @@ mod menu {
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn((Text::new("Volume"), button_text_style.clone()));
|
||||||
"Volume",
|
|
||||||
button_text_style.clone(),
|
|
||||||
));
|
|
||||||
for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
|
for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
|
||||||
let mut entity = parent.spawn((
|
let mut entity = parent.spawn((
|
||||||
ButtonBundle {
|
ButtonBundle {
|
||||||
|
@ -769,9 +753,7 @@ mod menu {
|
||||||
},
|
},
|
||||||
MenuButtonAction::BackToSettings,
|
MenuButtonAction::BackToSettings,
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_child((Text::new("Back"), button_text_style));
|
||||||
parent.spawn(TextBundle::from_section("Back", button_text_style));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,12 +91,7 @@ fn setup(mut commands: Commands) {
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_child((Text::new("Press 1 or 2 to load a new scene."), text_style));
|
||||||
parent.spawn(TextBundle::from_section(
|
|
||||||
"Press 1 or 2 to load a new scene.",
|
|
||||||
text_style,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selects the level you want to load.
|
// Selects the level you want to load.
|
||||||
|
@ -275,12 +270,7 @@ fn load_loading_screen(mut commands: Commands) {
|
||||||
},
|
},
|
||||||
LoadingScreen,
|
LoadingScreen,
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_child((Text::new("Loading..."), text_style.clone()));
|
||||||
parent.spawn(TextBundle::from_sections([TextSection::new(
|
|
||||||
"Loading...",
|
|
||||||
text_style.clone(),
|
|
||||||
)]));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determines when to show the loading screen
|
// Determines when to show the loading screen
|
||||||
|
|
|
@ -103,7 +103,7 @@ fn build_ui(
|
||||||
mut stepping: ResMut<Stepping>,
|
mut stepping: ResMut<Stepping>,
|
||||||
mut state: ResMut<State>,
|
mut state: ResMut<State>,
|
||||||
) {
|
) {
|
||||||
let mut text_sections = Vec::new();
|
let mut text_spans = Vec::new();
|
||||||
let mut always_run = Vec::new();
|
let mut always_run = Vec::new();
|
||||||
|
|
||||||
let Ok(schedule_order) = stepping.schedules() else {
|
let Ok(schedule_order) = stepping.schedules() else {
|
||||||
|
@ -114,8 +114,8 @@ fn build_ui(
|
||||||
// each label
|
// each label
|
||||||
for label in schedule_order {
|
for label in schedule_order {
|
||||||
let schedule = schedules.get(*label).unwrap();
|
let schedule = schedules.get(*label).unwrap();
|
||||||
text_sections.push(TextSection::new(
|
text_spans.push((
|
||||||
format!("{label:?}\n"),
|
TextSpan(format!("{label:?}\n")),
|
||||||
TextStyle {
|
TextStyle {
|
||||||
font: asset_server.load(FONT_BOLD),
|
font: asset_server.load(FONT_BOLD),
|
||||||
color: FONT_COLOR,
|
color: FONT_COLOR,
|
||||||
|
@ -138,11 +138,12 @@ fn build_ui(
|
||||||
|
|
||||||
// Add an entry to our systems list so we can find where to draw
|
// Add an entry to our systems list so we can find where to draw
|
||||||
// the cursor when the stepping cursor is at this system
|
// the cursor when the stepping cursor is at this system
|
||||||
state.systems.push((*label, node_id, text_sections.len()));
|
// we add plus 1 to account for the empty root span
|
||||||
|
state.systems.push((*label, node_id, text_spans.len() + 1));
|
||||||
|
|
||||||
// Add a text section for displaying the cursor for this system
|
// Add a text section for displaying the cursor for this system
|
||||||
text_sections.push(TextSection::new(
|
text_spans.push((
|
||||||
" ",
|
TextSpan::new(" "),
|
||||||
TextStyle {
|
TextStyle {
|
||||||
color: FONT_COLOR,
|
color: FONT_COLOR,
|
||||||
..default()
|
..default()
|
||||||
|
@ -150,8 +151,8 @@ fn build_ui(
|
||||||
));
|
));
|
||||||
|
|
||||||
// add the name of the system to the ui
|
// add the name of the system to the ui
|
||||||
text_sections.push(TextSection::new(
|
text_spans.push((
|
||||||
format!("{}\n", system.name()),
|
TextSpan(format!("{}\n", system.name())),
|
||||||
TextStyle {
|
TextStyle {
|
||||||
color: FONT_COLOR,
|
color: FONT_COLOR,
|
||||||
..default()
|
..default()
|
||||||
|
@ -164,22 +165,25 @@ fn build_ui(
|
||||||
stepping.always_run_node(label, node);
|
stepping.always_run_node(label, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.spawn((
|
commands
|
||||||
SteppingUi,
|
.spawn((
|
||||||
TextBundle {
|
Text::default(),
|
||||||
text: Text::from_sections(text_sections),
|
SteppingUi,
|
||||||
style: Style {
|
Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: state.ui_top,
|
top: state.ui_top,
|
||||||
left: state.ui_left,
|
left: state.ui_left,
|
||||||
padding: UiRect::all(Val::Px(10.0)),
|
padding: UiRect::all(Val::Px(10.0)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
background_color: BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.33)),
|
BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.33)),
|
||||||
visibility: Visibility::Hidden,
|
Visibility::Hidden,
|
||||||
..default()
|
))
|
||||||
},
|
.with_children(|p| {
|
||||||
));
|
for span in text_spans {
|
||||||
|
p.spawn(span);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_stepping_hint(mut commands: Commands) {
|
fn build_stepping_hint(mut commands: Commands) {
|
||||||
|
@ -190,20 +194,20 @@ fn build_stepping_hint(mut commands: Commands) {
|
||||||
};
|
};
|
||||||
info!("{}", hint_text);
|
info!("{}", hint_text);
|
||||||
// stepping description box
|
// stepping description box
|
||||||
commands.spawn((TextBundle::from_sections([TextSection::new(
|
commands.spawn((
|
||||||
hint_text,
|
Text::new(hint_text),
|
||||||
TextStyle {
|
TextStyle {
|
||||||
font_size: 15.0,
|
font_size: 15.0,
|
||||||
color: FONT_COLOR,
|
color: FONT_COLOR,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
)])
|
Style {
|
||||||
.with_style(Style {
|
position_type: PositionType::Absolute,
|
||||||
position_type: PositionType::Absolute,
|
bottom: Val::Px(5.0),
|
||||||
bottom: Val::Px(5.0),
|
left: Val::Px(5.0),
|
||||||
left: Val::Px(5.0),
|
..default()
|
||||||
..default()
|
},
|
||||||
}),));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_input(keyboard_input: Res<ButtonInput<KeyCode>>, mut stepping: ResMut<Stepping>) {
|
fn handle_input(keyboard_input: Res<ButtonInput<KeyCode>>, mut stepping: ResMut<Stepping>) {
|
||||||
|
@ -239,14 +243,15 @@ fn update_ui(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
state: Res<State>,
|
state: Res<State>,
|
||||||
stepping: Res<Stepping>,
|
stepping: Res<Stepping>,
|
||||||
mut ui: Query<(Entity, &mut Text, &Visibility), With<SteppingUi>>,
|
ui: Query<(Entity, &Visibility), With<SteppingUi>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
if ui.is_empty() {
|
if ui.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure the UI is only visible when stepping is enabled
|
// ensure the UI is only visible when stepping is enabled
|
||||||
let (ui, mut text, vis) = ui.single_mut();
|
let (ui, vis) = ui.single();
|
||||||
match (vis, stepping.is_enabled()) {
|
match (vis, stepping.is_enabled()) {
|
||||||
(Visibility::Hidden, true) => {
|
(Visibility::Hidden, true) => {
|
||||||
commands.entity(ui).insert(Visibility::Inherited);
|
commands.entity(ui).insert(Visibility::Inherited);
|
||||||
|
@ -274,6 +279,6 @@ fn update_ui(
|
||||||
} else {
|
} else {
|
||||||
" "
|
" "
|
||||||
};
|
};
|
||||||
text.sections[*text_index].value = mark.to_string();
|
*writer.text(ui, *text_index) = mark.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,22 +20,21 @@ struct MyRoundGizmos {}
|
||||||
fn setup(mut commands: Commands) {
|
fn setup(mut commands: Commands) {
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
// text
|
// text
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new(
|
||||||
"Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
|
"Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
|
||||||
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
|
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
|
||||||
Press '1' / '2' to toggle the visibility of straight / round gizmos\n\
|
Press '1' / '2' to toggle the visibility of straight / round gizmos\n\
|
||||||
Press 'U' / 'I' to cycle through line styles\n\
|
Press 'U' / 'I' to cycle through line styles\n\
|
||||||
Press 'J' / 'K' to cycle through line joins",
|
Press 'J' / 'K' to cycle through line joins",
|
||||||
TextStyle::default(),
|
),
|
||||||
)
|
Style {
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.),
|
top: Val::Px(12.),
|
||||||
left: Val::Px(12.),
|
left: Val::Px(12.),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_example_collection(
|
fn draw_example_collection(
|
||||||
|
|
|
@ -51,8 +51,8 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// example instructions
|
// example instructions
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new(
|
||||||
"Press 'T' to toggle drawing gizmos on top of everything else in the scene\n\
|
"Press 'T' to toggle drawing gizmos on top of everything else in the scene\n\
|
||||||
Press 'P' to toggle perspective for line gizmos\n\
|
Press 'P' to toggle perspective for line gizmos\n\
|
||||||
Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
|
Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
|
||||||
|
@ -61,15 +61,14 @@ fn setup(
|
||||||
Press 'B' to show all AABB boxes\n\
|
Press 'B' to show all AABB boxes\n\
|
||||||
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
|
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
|
||||||
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
|
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
|
||||||
TextStyle::default(),
|
),
|
||||||
)
|
Style {
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_example_collection(
|
fn draw_example_collection(
|
||||||
|
|
|
@ -102,41 +102,37 @@ fn setup(
|
||||||
|
|
||||||
// Example instructions and gizmo config.
|
// Example instructions and gizmo config.
|
||||||
{
|
{
|
||||||
let text_style = TextStyle::default();
|
commands.spawn((
|
||||||
|
Text::new(
|
||||||
commands.spawn(
|
|
||||||
TextBundle::from_section(
|
|
||||||
"Press 'D' to toggle drawing gizmos on top of everything else in the scene\n\
|
"Press 'D' to toggle drawing gizmos on top of everything else in the scene\n\
|
||||||
Hold 'Left' or 'Right' to change the line width of the gizmos\n\
|
Hold 'Left' or 'Right' to change the line width of the gizmos\n\
|
||||||
Press 'A' to toggle drawing of the light gizmos\n\
|
Press 'A' to toggle drawing of the light gizmos\n\
|
||||||
Press 'C' to cycle between the light gizmos coloring modes",
|
Press 'C' to cycle between the light gizmos coloring modes",
|
||||||
text_style.clone(),
|
),
|
||||||
)
|
Style {
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
let (_, light_config) = config_store.config_mut::<LightGizmoConfigGroup>();
|
let (_, light_config) = config_store.config_mut::<LightGizmoConfigGroup>();
|
||||||
light_config.draw_all = true;
|
light_config.draw_all = true;
|
||||||
light_config.color = LightGizmoColor::MatchLightColor;
|
light_config.color = LightGizmoColor::MatchLightColor;
|
||||||
|
|
||||||
commands.spawn((
|
commands
|
||||||
TextBundle::from_sections([
|
.spawn((
|
||||||
TextSection::new("Gizmo color mode: ", text_style.clone()),
|
Text::new("Gizmo color mode: "),
|
||||||
TextSection::new(gizmo_color_text(light_config), text_style),
|
GizmoColorText,
|
||||||
])
|
Style {
|
||||||
.with_style(Style {
|
position_type: PositionType::Absolute,
|
||||||
position_type: PositionType::Absolute,
|
bottom: Val::Px(12.0),
|
||||||
bottom: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
..default()
|
||||||
..default()
|
},
|
||||||
}),
|
))
|
||||||
GizmoColorText,
|
.with_child(TextSpan(gizmo_color_text(light_config)));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +146,8 @@ fn update_config(
|
||||||
mut config_store: ResMut<GizmoConfigStore>,
|
mut config_store: ResMut<GizmoConfigStore>,
|
||||||
keyboard: Res<ButtonInput<KeyCode>>,
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut color_text_query: Query<&mut Text, With<GizmoColorText>>,
|
color_text_query: Query<Entity, With<GizmoColorText>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
if keyboard.just_pressed(KeyCode::KeyD) {
|
if keyboard.just_pressed(KeyCode::KeyD) {
|
||||||
for (_, config, _) in config_store.iter_mut() {
|
for (_, config, _) in config_store.iter_mut() {
|
||||||
|
@ -177,6 +174,6 @@ fn update_config(
|
||||||
LightGizmoColor::MatchLightColor => LightGizmoColor::ByLightType,
|
LightGizmoColor::MatchLightColor => LightGizmoColor::ByLightType,
|
||||||
LightGizmoColor::ByLightType => LightGizmoColor::Manual(GRAY.into()),
|
LightGizmoColor::ByLightType => LightGizmoColor::Manual(GRAY.into()),
|
||||||
};
|
};
|
||||||
color_text_query.single_mut().sections[1].value = gizmo_color_text(light_config);
|
*writer.text(color_text_query.single(), 1) = gizmo_color_text(light_config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,8 +132,8 @@ pub fn spawn_ui_text<'a>(
|
||||||
label: &str,
|
label: &str,
|
||||||
color: Color,
|
color: Color,
|
||||||
) -> EntityCommands<'a> {
|
) -> EntityCommands<'a> {
|
||||||
parent.spawn(TextBundle::from_section(
|
parent.spawn((
|
||||||
label,
|
Text::new(label),
|
||||||
TextStyle {
|
TextStyle {
|
||||||
font_size: 18.0,
|
font_size: 18.0,
|
||||||
color,
|
color,
|
||||||
|
@ -168,10 +168,10 @@ pub fn update_ui_radio_button(background_color: &mut BackgroundColor, selected:
|
||||||
|
|
||||||
/// Updates the style of the label of a radio button to reflect its selected
|
/// Updates the style of the label of a radio button to reflect its selected
|
||||||
/// status.
|
/// status.
|
||||||
pub fn update_ui_radio_button_text(text: &mut Text, selected: bool) {
|
pub fn update_ui_radio_button_text(entity: Entity, writer: &mut UiTextWriter, selected: bool) {
|
||||||
let text_color = if selected { Color::BLACK } else { Color::WHITE };
|
let text_color = if selected { Color::BLACK } else { Color::WHITE };
|
||||||
|
|
||||||
for section in &mut text.sections {
|
writer.for_each_style(entity, |mut style| {
|
||||||
section.style.color = text_color;
|
style.color = text_color;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,47 +34,49 @@ fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
// sections that will hold text input.
|
// sections that will hold text input.
|
||||||
let font = asset_server.load("fonts/FiraMono-Medium.ttf");
|
let font = asset_server.load("fonts/FiraMono-Medium.ttf");
|
||||||
|
|
||||||
commands.spawn(
|
commands
|
||||||
TextBundle::from_sections([
|
.spawn((
|
||||||
TextSection::from("Click to toggle IME. Press return to start a new line.\n\n"),
|
Text::default(),
|
||||||
TextSection::from("IME Enabled: "),
|
Style {
|
||||||
TextSection::from("false\n"),
|
position_type: PositionType::Absolute,
|
||||||
TextSection::from("IME Active: "),
|
top: Val::Px(12.0),
|
||||||
TextSection::from("false\n"),
|
left: Val::Px(12.0),
|
||||||
TextSection::from("IME Buffer: "),
|
..default()
|
||||||
TextSection {
|
},
|
||||||
value: "\n".to_string(),
|
))
|
||||||
style: TextStyle {
|
.with_children(|p| {
|
||||||
|
p.spawn(TextSpan::new(
|
||||||
|
"Click to toggle IME. Press return to start a new line.\n\n",
|
||||||
|
));
|
||||||
|
p.spawn(TextSpan::new("IME Enabled: "));
|
||||||
|
p.spawn(TextSpan::new("false\n"));
|
||||||
|
p.spawn(TextSpan::new("IME Active: "));
|
||||||
|
p.spawn(TextSpan::new("false\n"));
|
||||||
|
p.spawn(TextSpan::new("IME Buffer: "));
|
||||||
|
p.spawn((
|
||||||
|
TextSpan::new("\n"),
|
||||||
|
TextStyle {
|
||||||
font: font.clone(),
|
font: font.clone(),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
},
|
));
|
||||||
])
|
});
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
top: Val::Px(12.0),
|
|
||||||
left: Val::Px(12.0),
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
commands.spawn(Text2dBundle {
|
commands.spawn((
|
||||||
text: Text::from_section(
|
Text2d::new(""),
|
||||||
"".to_string(),
|
TextStyle {
|
||||||
TextStyle {
|
font,
|
||||||
font,
|
font_size: 100.0,
|
||||||
font_size: 100.0,
|
..default()
|
||||||
..default()
|
},
|
||||||
},
|
));
|
||||||
),
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_ime(
|
fn toggle_ime(
|
||||||
input: Res<ButtonInput<MouseButton>>,
|
input: Res<ButtonInput<MouseButton>>,
|
||||||
mut windows: Query<&mut Window>,
|
mut windows: Query<&mut Window>,
|
||||||
mut text: Query<&mut Text, With<Node>>,
|
status_text: Query<Entity, (With<Node>, With<Text>)>,
|
||||||
|
mut ui_writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
if input.just_pressed(MouseButton::Left) {
|
if input.just_pressed(MouseButton::Left) {
|
||||||
let mut window = windows.single_mut();
|
let mut window = windows.single_mut();
|
||||||
|
@ -82,8 +84,7 @@ fn toggle_ime(
|
||||||
window.ime_position = window.cursor_position().unwrap();
|
window.ime_position = window.cursor_position().unwrap();
|
||||||
window.ime_enabled = !window.ime_enabled;
|
window.ime_enabled = !window.ime_enabled;
|
||||||
|
|
||||||
let mut text = text.single_mut();
|
*ui_writer.text(status_text.single(), 3) = format!("{}\n", window.ime_enabled);
|
||||||
text.sections[2].value = format!("{}\n", window.ime_enabled);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,25 +108,26 @@ fn bubbling_text(
|
||||||
|
|
||||||
fn listen_ime_events(
|
fn listen_ime_events(
|
||||||
mut events: EventReader<Ime>,
|
mut events: EventReader<Ime>,
|
||||||
mut status_text: Query<&mut Text, With<Node>>,
|
status_text: Query<Entity, (With<Node>, With<Text>)>,
|
||||||
mut edit_text: Query<&mut Text, (Without<Node>, Without<Bubble>)>,
|
mut edit_text: Query<&mut Text2d, (Without<Node>, Without<Bubble>)>,
|
||||||
|
mut ui_writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
match event {
|
match event {
|
||||||
Ime::Preedit { value, cursor, .. } if !cursor.is_none() => {
|
Ime::Preedit { value, cursor, .. } if !cursor.is_none() => {
|
||||||
status_text.single_mut().sections[6].value = format!("{value}\n");
|
*ui_writer.text(status_text.single(), 7) = format!("{value}\n");
|
||||||
}
|
}
|
||||||
Ime::Preedit { cursor, .. } if cursor.is_none() => {
|
Ime::Preedit { cursor, .. } if cursor.is_none() => {
|
||||||
status_text.single_mut().sections[6].value = "\n".to_string();
|
*ui_writer.text(status_text.single(), 7) = "\n".to_string();
|
||||||
}
|
}
|
||||||
Ime::Commit { value, .. } => {
|
Ime::Commit { value, .. } => {
|
||||||
edit_text.single_mut().sections[0].value.push_str(value);
|
edit_text.single_mut().push_str(value);
|
||||||
}
|
}
|
||||||
Ime::Enabled { .. } => {
|
Ime::Enabled { .. } => {
|
||||||
status_text.single_mut().sections[4].value = "true\n".to_string();
|
*ui_writer.text(status_text.single(), 5) = "true\n".to_string();
|
||||||
}
|
}
|
||||||
Ime::Disabled { .. } => {
|
Ime::Disabled { .. } => {
|
||||||
status_text.single_mut().sections[4].value = "false\n".to_string();
|
*ui_writer.text(status_text.single(), 5) = "false\n".to_string();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -135,7 +137,7 @@ fn listen_ime_events(
|
||||||
fn listen_keyboard_input_events(
|
fn listen_keyboard_input_events(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut events: EventReader<KeyboardInput>,
|
mut events: EventReader<KeyboardInput>,
|
||||||
mut edit_text: Query<&mut Text, (Without<Node>, Without<Bubble>)>,
|
mut edit_text: Query<(&mut Text2d, &TextStyle), (Without<Node>, Without<Bubble>)>,
|
||||||
) {
|
) {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
// Only trigger changes when the key is first pressed.
|
// Only trigger changes when the key is first pressed.
|
||||||
|
@ -145,30 +147,28 @@ fn listen_keyboard_input_events(
|
||||||
|
|
||||||
match &event.logical_key {
|
match &event.logical_key {
|
||||||
Key::Enter => {
|
Key::Enter => {
|
||||||
let mut text = edit_text.single_mut();
|
let (mut text, style) = edit_text.single_mut();
|
||||||
if text.sections[0].value.is_empty() {
|
if text.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let old_value = mem::take(&mut text.sections[0].value);
|
let old_value = mem::take(&mut **text);
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Text2dBundle {
|
Text2d::new(old_value),
|
||||||
text: Text::from_section(old_value, text.sections[0].style.clone()),
|
style.clone(),
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Bubble {
|
Bubble {
|
||||||
timer: Timer::from_seconds(5.0, TimerMode::Once),
|
timer: Timer::from_seconds(5.0, TimerMode::Once),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Key::Space => {
|
Key::Space => {
|
||||||
edit_text.single_mut().sections[0].value.push(' ');
|
edit_text.single_mut().0.push(' ');
|
||||||
}
|
}
|
||||||
Key::Backspace => {
|
Key::Backspace => {
|
||||||
edit_text.single_mut().sections[0].value.pop();
|
edit_text.single_mut().0.pop();
|
||||||
}
|
}
|
||||||
Key::Character(character) => {
|
Key::Character(character) => {
|
||||||
edit_text.single_mut().sections[0].value.push_str(character);
|
edit_text.single_mut().0.push_str(character);
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,15 +90,9 @@ fn setup(mut commands: Commands) {
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(TextBundle::from_section(instructions_text, style.clone()));
|
parent.spawn((Text::new(instructions_text), style.clone()));
|
||||||
parent.spawn((
|
parent.spawn((SplineModeText, Text(spline_mode_text), style.clone()));
|
||||||
SplineModeText,
|
parent.spawn((CyclingModeText, Text(cycling_mode_text), style.clone()));
|
||||||
TextBundle::from_section(spline_mode_text, style.clone()),
|
|
||||||
));
|
|
||||||
parent.spawn((
|
|
||||||
CyclingModeText,
|
|
||||||
TextBundle::from_section(cycling_mode_text, style.clone()),
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,9 +258,7 @@ fn update_spline_mode_text(
|
||||||
let new_text = format!("Spline: {}", *spline_mode);
|
let new_text = format!("Spline: {}", *spline_mode);
|
||||||
|
|
||||||
for mut spline_mode_text in spline_mode_text.iter_mut() {
|
for mut spline_mode_text in spline_mode_text.iter_mut() {
|
||||||
if let Some(section) = spline_mode_text.sections.first_mut() {
|
(**spline_mode_text).clone_from(&new_text);
|
||||||
section.value.clone_from(&new_text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,9 +273,7 @@ fn update_cycling_mode_text(
|
||||||
let new_text = format!("{}", *cycling_mode);
|
let new_text = format!("{}", *cycling_mode);
|
||||||
|
|
||||||
for mut cycling_mode_text in cycling_mode_text.iter_mut() {
|
for mut cycling_mode_text in cycling_mode_text.iter_mut() {
|
||||||
if let Some(section) = cycling_mode_text.sections.first_mut() {
|
(**cycling_mode_text).clone_from(&new_text);
|
||||||
section.value.clone_from(&new_text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,19 +156,14 @@ fn setup(
|
||||||
));
|
));
|
||||||
|
|
||||||
// Example instructions
|
// Example instructions
|
||||||
commands.spawn(
|
commands.spawn((Text::new("Press 'B' to toggle between no bounding shapes, bounding boxes (AABBs) and bounding spheres / circles\n\
|
||||||
TextBundle::from_section(
|
Press 'Space' to switch between 3D and 2D"),
|
||||||
"Press 'B' to toggle between no bounding shapes, bounding boxes (AABBs) and bounding spheres / circles\n\
|
Style {
|
||||||
Press 'Space' to switch between 3D and 2D",
|
|
||||||
TextStyle::default(),
|
|
||||||
)
|
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate the 2D shapes.
|
// Rotate the 2D shapes.
|
||||||
|
|
|
@ -109,23 +109,22 @@ fn setup(
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Instructions for the example:
|
// Instructions for the example:
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new(
|
||||||
"Controls:\n\
|
"Controls:\n\
|
||||||
M: Toggle between sampling boundary and interior.\n\
|
M: Toggle between sampling boundary and interior.\n\
|
||||||
R: Restart (erase all samples).\n\
|
R: Restart (erase all samples).\n\
|
||||||
S: Add one random sample.\n\
|
S: Add one random sample.\n\
|
||||||
D: Add 100 random samples.\n\
|
D: Add 100 random samples.\n\
|
||||||
Rotate camera by holding left mouse and panning left/right.",
|
Rotate camera by holding left mouse and panning left/right.",
|
||||||
TextStyle::default(),
|
),
|
||||||
)
|
Style {
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
// The mode starts with interior points.
|
// The mode starts with interior points.
|
||||||
commands.insert_resource(Mode::Interior);
|
commands.insert_resource(Mode::Interior);
|
||||||
|
|
|
@ -367,22 +367,6 @@ fn setup_text(mut commands: Commands, cameras: Query<(Entity, &Camera)>) {
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|(entity, camera)| camera.is_active.then_some(entity))
|
.find_map(|(entity, camera)| camera.is_active.then_some(entity))
|
||||||
.expect("run condition ensures existence");
|
.expect("run condition ensures existence");
|
||||||
let text = format!("{text}", text = PrimitiveSelected::default());
|
|
||||||
let style = TextStyle::default();
|
|
||||||
let instructions = "Press 'C' to switch between 2D and 3D mode\n\
|
|
||||||
Press 'Up' or 'Down' to switch to the next/previous primitive";
|
|
||||||
let text = [
|
|
||||||
TextSection::new("Primitive: ", style.clone()),
|
|
||||||
TextSection::new(text, style.clone()),
|
|
||||||
TextSection::new("\n\n", style.clone()),
|
|
||||||
TextSection::new(instructions, style.clone()),
|
|
||||||
TextSection::new("\n\n", style.clone()),
|
|
||||||
TextSection::new(
|
|
||||||
"(If nothing is displayed, there's no rendering support yet)",
|
|
||||||
style.clone(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
HeaderNode,
|
HeaderNode,
|
||||||
|
@ -396,22 +380,40 @@ fn setup_text(mut commands: Commands, cameras: Query<(Entity, &Camera)>) {
|
||||||
},
|
},
|
||||||
TargetCamera(active_camera),
|
TargetCamera(active_camera),
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|p| {
|
||||||
parent.spawn((
|
p.spawn((
|
||||||
|
Text::default(),
|
||||||
HeaderText,
|
HeaderText,
|
||||||
TextBundle::from_sections(text).with_text_justify(JustifyText::Center),
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
));
|
))
|
||||||
|
.with_children(|p| {
|
||||||
|
p.spawn(TextSpan::new("Primitive: "));
|
||||||
|
p.spawn(TextSpan(format!(
|
||||||
|
"{text}",
|
||||||
|
text = PrimitiveSelected::default()
|
||||||
|
)));
|
||||||
|
p.spawn(TextSpan::new("\n\n"));
|
||||||
|
p.spawn(TextSpan::new(
|
||||||
|
"Press 'C' to switch between 2D and 3D mode\n\
|
||||||
|
Press 'Up' or 'Down' to switch to the next/previous primitive",
|
||||||
|
));
|
||||||
|
p.spawn(TextSpan::new("\n\n"));
|
||||||
|
p.spawn(TextSpan::new(
|
||||||
|
"(If nothing is displayed, there's no rendering support yet)",
|
||||||
|
));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_text(
|
fn update_text(
|
||||||
primitive_state: Res<State<PrimitiveSelected>>,
|
primitive_state: Res<State<PrimitiveSelected>>,
|
||||||
mut header: Query<&mut Text, With<HeaderText>>,
|
header: Query<Entity, With<HeaderText>>,
|
||||||
|
mut writer: UiTextWriter,
|
||||||
) {
|
) {
|
||||||
let new_text = format!("{text}", text = primitive_state.get());
|
let new_text = format!("{text}", text = primitive_state.get());
|
||||||
header.iter_mut().for_each(|mut header_text| {
|
header.iter().for_each(|header_text| {
|
||||||
if let Some(kind) = header_text.sections.get_mut(1) {
|
if let Some(mut text) = writer.get_text(header_text, 2) {
|
||||||
kind.value.clone_from(&new_text);
|
(*text).clone_from(&new_text);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,8 +375,8 @@ fn setup(
|
||||||
});
|
});
|
||||||
|
|
||||||
// Instructions for the example:
|
// Instructions for the example:
|
||||||
commands.spawn(
|
commands.spawn((
|
||||||
TextBundle::from_section(
|
Text::new(
|
||||||
"Controls:\n\
|
"Controls:\n\
|
||||||
M: Toggle between sampling boundary and interior.\n\
|
M: Toggle between sampling boundary and interior.\n\
|
||||||
A: Toggle automatic spawning & despawning of points.\n\
|
A: Toggle automatic spawning & despawning of points.\n\
|
||||||
|
@ -387,15 +387,14 @@ fn setup(
|
||||||
Zoom camera by scrolling via mouse or +/-.\n\
|
Zoom camera by scrolling via mouse or +/-.\n\
|
||||||
Move camera by L/R arrow keys.\n\
|
Move camera by L/R arrow keys.\n\
|
||||||
Tab: Toggle this text",
|
Tab: Toggle this text",
|
||||||
TextStyle::default(),
|
),
|
||||||
)
|
Style {
|
||||||
.with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Px(12.0),
|
top: Val::Px(12.0),
|
||||||
left: Val::Px(12.0),
|
left: Val::Px(12.0),
|
||||||
..default()
|
..default()
|
||||||
}),
|
},
|
||||||
);
|
));
|
||||||
|
|
||||||
// No points are scheduled to spawn initially.
|
// No points are scheduled to spawn initially.
|
||||||
commands.insert_resource(SpawnQueue(0));
|
commands.insert_resource(SpawnQueue(0));
|
||||||
|
|
|
@ -120,19 +120,15 @@ fn setup_scene(
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|b| {
|
.with_child((
|
||||||
b.spawn(
|
Text::new("Test Button"),
|
||||||
TextBundle::from_section(
|
TextStyle {
|
||||||
"Test Button",
|
font_size: 30.0,
|
||||||
TextStyle {
|
color: Color::BLACK,
|
||||||
font_size: 30.0,
|
..default()
|
||||||
color: Color::BLACK,
|
},
|
||||||
..default()
|
TextBlock::new_with_justify(JustifyText::Center),
|
||||||
},
|
));
|
||||||
)
|
|
||||||
.with_text_justify(JustifyText::Center),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn button_handler(
|
fn button_handler(
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue