diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index eaaf58d2b5273..9975d2d97e1b4 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -106,6 +106,13 @@ impl Text { self.alignment = alignment; self } + + /// Returns this [`Text`] 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.linebreak_behavior = BreakLineOn::NoWrap; + self + } } #[derive(Debug, Default, Clone, FromReflect, Reflect)] @@ -186,12 +193,19 @@ pub enum BreakLineOn { /// This is closer to the behavior one might expect from text in a terminal. /// However it may lead to words being broken up across linebreaks. AnyCharacter, + /// No soft wrapping, where text is automatically broken up into separate lines when it overflows a boundary, will ever occur. + /// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, is still enabled. + NoWrap, } impl From for glyph_brush_layout::BuiltInLineBreaker { fn from(val: BreakLineOn) -> Self { match val { - BreakLineOn::WordBoundary => glyph_brush_layout::BuiltInLineBreaker::UnicodeLineBreaker, + // If `NoWrap` is set the choice of `BuiltInLineBreaker` doesn't matter as the text is given unbounded width and soft wrapping will never occur. + // But `NoWrap` does not disable hard breaks where a [`Text`] contains a newline character. + BreakLineOn::WordBoundary | BreakLineOn::NoWrap => { + glyph_brush_layout::BuiltInLineBreaker::UnicodeLineBreaker + } BreakLineOn::AnyCharacter => glyph_brush_layout::BuiltInLineBreaker::AnyCharLineBreaker, } } diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 0cee62291d556..61f0354b591eb 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -23,8 +23,8 @@ use bevy_utils::HashSet; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; use crate::{ - Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError, TextLayoutInfo, - TextPipeline, TextSettings, YAxisOrientation, + BreakLineOn, Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError, + TextLayoutInfo, TextPipeline, TextSettings, YAxisOrientation, }; /// The maximum width and height of text. The text will wrap according to the specified size. @@ -174,7 +174,11 @@ pub fn update_text2d_layout( for (entity, text, bounds, mut text_layout_info) in &mut text_query { if factor_changed || text.is_changed() || bounds.is_changed() || queue.remove(&entity) { let text_bounds = Vec2::new( - scale_value(bounds.size.x, scale_factor), + if text.linebreak_behavior == BreakLineOn::NoWrap { + f32::INFINITY + } else { + scale_value(bounds.size.x, scale_factor) + }, scale_value(bounds.size.y, scale_factor), ); diff --git a/crates/bevy_ui/src/measurement.rs b/crates/bevy_ui/src/measurement.rs index 8c5959f27e31f..896c9f6c05505 100644 --- a/crates/bevy_ui/src/measurement.rs +++ b/crates/bevy_ui/src/measurement.rs @@ -28,7 +28,7 @@ pub trait Measure: Send + Sync + 'static { /// always returns the same size. #[derive(Default, Clone)] pub struct FixedMeasure { - size: Vec2, + pub size: Vec2, } impl Measure for FixedMeasure { diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 468a155aa0802..c42afda483b69 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -13,7 +13,7 @@ use bevy_render::{ }; use bevy_sprite::TextureAtlas; #[cfg(feature = "bevy_text")] -use bevy_text::{Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle}; +use bevy_text::{BreakLineOn, Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle}; use bevy_transform::prelude::{GlobalTransform, Transform}; /// The basic UI node @@ -256,6 +256,13 @@ impl TextBundle { 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_behavior = BreakLineOn::NoWrap; + self + } } /// A UI node that is a button diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 25ada49e9a3d6..72d2bbf0b3a36 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -1,4 +1,4 @@ -use crate::{ContentSize, Measure, Node, UiScale}; +use crate::{ContentSize, FixedMeasure, Measure, Node, UiScale}; use bevy_asset::Assets; use bevy_ecs::{ prelude::{Component, DetectChanges}, @@ -12,8 +12,8 @@ use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFrom use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_text::{ - Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextMeasureInfo, - TextPipeline, TextSettings, YAxisOrientation, + BreakLineOn, Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, + TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation, }; use bevy_window::{PrimaryWindow, Window}; use taffy::style::AvailableSpace; @@ -91,7 +91,13 @@ fn create_text_measure( text.linebreak_behavior, ) { Ok(measure) => { - content_size.set(TextMeasure { info: measure }); + if text.linebreak_behavior == BreakLineOn::NoWrap { + content_size.set(FixedMeasure { + size: measure.max_width_content_size, + }); + } else { + content_size.set(TextMeasure { info: measure }); + } // Text measure func created succesfully, so set `TextFlags` to schedule a recompute text_flags.needs_new_measure_func = false; @@ -174,7 +180,12 @@ fn queue_text( ) { // Skip the text node if it is waiting for a new measure func if !text_flags.needs_new_measure_func { - let physical_node_size = node.physical_size(scale_factor); + let physical_node_size = if text.linebreak_behavior == BreakLineOn::NoWrap { + // With `NoWrap` set, no constraints are placed on the width of the text. + Vec2::splat(f32::INFINITY) + } else { + node.physical_size(scale_factor) + }; match text_pipeline.queue_text( fonts, diff --git a/examples/ui/text_wrap_debug.rs b/examples/ui/text_wrap_debug.rs index 0ef93cf6c25cd..374799ec8189a 100644 --- a/examples/ui/text_wrap_debug.rs +++ b/examples/ui/text_wrap_debug.rs @@ -33,7 +33,11 @@ fn spawn(mut commands: Commands, asset_server: Res) { }) .id(); - for linebreak_behavior in [BreakLineOn::AnyCharacter, BreakLineOn::WordBoundary] { + for linebreak_behavior in [ + BreakLineOn::AnyCharacter, + BreakLineOn::WordBoundary, + BreakLineOn::NoWrap, + ] { let row_id = commands .spawn(NodeBundle { style: Style { @@ -66,7 +70,7 @@ fn spawn(mut commands: Commands, asset_server: Res) { flex_direction: FlexDirection::Column, width: Val::Percent(16.), height: Val::Percent(95.), - overflow: Overflow::clip(), + overflow: Overflow::clip_x(), ..Default::default() }, background_color: Color::rgb(0.5, c, 1.0 - c).into(),