Skip to content

Commit 250a7c0

Browse files
EditableText::visible_width (#23717)
# Objective Horizontal equivalent of the `visible_lines` option. Allow users to set the width of a text input widget by number of glyphs. Or for proportional fonts, where the glyph widths are non-uniform, it would use a multiple of the advance width for "0". ## Solution * New `visible_width: Option<f32>` field on `EditableText`. * `TextInputMeasure` now has both a width and height field and both are `Option<f32>`. * `update_editable_text_content_size` calculates the width if `visible_width` is `Some`. * The width is calculated by multiplying the advance width of "0" by the `visible_width` (if set) and the current scale factor. ## Testing I altered the hex input in the `feathers` example to so it's sized to fit 10 characters (includes space for the cursor). ``` cargo run --example feathers --features="experimental_bevy_feathers" ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent 695c8e0 commit 250a7c0

4 files changed

Lines changed: 93 additions & 13 deletions

File tree

crates/bevy_feathers/src/controls/text_input.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ struct FeathersTextInput;
3636

3737
/// Parameters for the text input template, passed to [`text_input`] function.
3838
pub struct TextInputProps {
39+
/// Visible width
40+
pub visible_width: Option<f32>,
3941
/// Max characters
4042
pub max_characters: Option<usize>,
4143
}
@@ -82,11 +84,18 @@ pub fn text_input_container() -> impl Scene {
8284
pub fn text_input(props: TextInputProps) -> impl Scene {
8385
bsn! {
8486
Node {
85-
flex_grow: 1.0,
87+
flex_grow: {
88+
if props.visible_width.is_some() {
89+
0.
90+
} else {
91+
1.
92+
}
93+
} ,
8694
}
8795
FeathersTextInput
8896
EditableText {
8997
cursor_width: 0.3,
98+
visible_width: {props.visible_width},
9099
max_characters: {props.max_characters},
91100
}
92101
TextLayout {

crates/bevy_text/src/text_editable.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ pub struct EditableText {
126126
pub max_characters: Option<usize>,
127127
/// Sets the input’s height in number of visible lines.
128128
pub visible_lines: Option<f32>,
129+
/// Sets the input's width in number of visible glyphs.
130+
/// For proportional fonts the final size is the given value times the "0" advance width.
131+
pub visible_width: Option<f32>,
129132
/// Allow new lines
130133
pub allow_newlines: bool,
131134
}
@@ -141,6 +144,7 @@ impl Default for EditableText {
141144
text_edited: false,
142145
max_characters: None,
143146
visible_lines: Some(1.),
147+
visible_width: None,
144148
allow_newlines: false,
145149
}
146150
}

crates/bevy_ui/src/widget/text_editable.rs

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ use taffy::MaybeMath;
3030
pub struct TextScroll(pub Vec2);
3131

3232
struct TextInputMeasure {
33-
height: f32,
33+
width: Option<f32>,
34+
height: Option<f32>,
3435
}
3536

3637
impl crate::Measure for TextInputMeasure {
@@ -40,22 +41,26 @@ impl crate::Measure for TextInputMeasure {
4041

4142
let x = width
4243
.effective
43-
.unwrap_or(match measure_args.available_width {
44+
.unwrap_or(self.width.unwrap_or(match measure_args.available_width {
4445
crate::AvailableSpace::Definite(x) => x,
4546
crate::AvailableSpace::MinContent | crate::AvailableSpace::MaxContent => 0.0,
46-
})
47+
}))
4748
.maybe_clamp(width.min, width.max);
4849
let y = height
4950
.effective
50-
.unwrap_or(self.height)
51+
.unwrap_or(self.height.unwrap_or(match measure_args.available_height {
52+
crate::AvailableSpace::Definite(y) => y,
53+
crate::AvailableSpace::MinContent | crate::AvailableSpace::MaxContent => 0.0,
54+
}))
5155
.maybe_clamp(height.min, height.max);
5256

5357
Vec2::new(x, y).ceil()
5458
}
5559
}
5660

57-
/// If `visible_lines` is `Some`, sets a `ContentSize` that determines the node's height
58-
/// as `line_height * visible_lines`, using the resolved font line height.
61+
/// If `visible_lines` or `visible_width` are `Some`, sets a `ContentSize` that determines:
62+
/// - node height as `line_height * visible_lines`, using the resolved font line height.
63+
/// - node width as `advance('0') * visible_width`, where `advance('0')` is looked up from font metrics.
5964
pub fn update_editable_text_content_size(
6065
mut text_input_query: Query<(
6166
Ref<EditableText>,
@@ -65,6 +70,7 @@ pub fn update_editable_text_content_size(
6570
&mut ContentSize,
6671
)>,
6772
fonts: Res<Assets<Font>>,
73+
mut font_cx: ResMut<FontCx>,
6874
rem_size: Res<RemSize>,
6975
) {
7076
for (editable_text, text_font, line_height, target, mut content_size) in &mut text_input_query {
@@ -78,17 +84,72 @@ pub fn update_editable_text_content_size(
7884
continue;
7985
}
8086

81-
if let Some(visible_lines) = editable_text.visible_lines {
82-
let font_size = text_font.font_size.eval(target.logical_size(), rem_size.0);
87+
let font_size = text_font.font_size.eval(target.logical_size(), rem_size.0);
88+
89+
let width = editable_text.visible_width.and_then(|visible_width| {
90+
let font_context = &mut font_cx.0;
91+
let mut query = font_context
92+
.collection
93+
.query(&mut font_context.source_cache);
94+
match resolve_font_source(&text_font.font, fonts.as_ref()).ok()? {
95+
parley::FontFamily::Single(parley::FontFamilyName::Named(name)) => {
96+
query.set_families([parley::fontique::QueryFamily::Named(name.as_ref())]);
97+
}
98+
parley::FontFamily::Single(parley::FontFamilyName::Generic(generic)) => {
99+
query.set_families([parley::fontique::QueryFamily::Generic(generic)]);
100+
}
101+
_ => return None,
102+
}
103+
query.set_attributes(parley::fontique::Attributes::new(
104+
text_font.width.into(),
105+
text_font.style.into(),
106+
text_font.weight.into(),
107+
));
108+
109+
let mut width = None;
110+
query.matches_with(|query_font| {
111+
let Some((glyph_id, font_ref)) = query_font
112+
.charmap()
113+
.and_then(|char_map| char_map.map('0'))
114+
.and_then(|glyph_id| u16::try_from(glyph_id).ok())
115+
.zip(FontRef::from_index(
116+
query_font.blob.as_ref(),
117+
query_font.index as usize,
118+
))
119+
else {
120+
return parley::fontique::QueryStatus::Continue;
121+
};
122+
123+
let advance = font_ref
124+
.glyph_metrics(&[])
125+
.scale(font_size)
126+
.advance_width(glyph_id);
127+
if advance.is_finite() {
128+
width = Some(advance.max(0.0));
129+
parley::fontique::QueryStatus::Stop
130+
} else {
131+
parley::fontique::QueryStatus::Continue
132+
}
133+
});
134+
135+
width.map(|width| width * visible_width * target.scale_factor())
136+
});
137+
138+
let height = editable_text.visible_lines.map(|visible_lines| {
83139
let logical_line_height = match *line_height {
84140
LineHeight::Px(px) => px,
85141
LineHeight::RelativeToFont(scale) => scale * font_size,
86142
};
143+
visible_lines * logical_line_height * target.scale_factor()
144+
});
145+
146+
if width.is_some() || height.is_some() {
87147
content_size.set(NodeMeasure::Custom(Box::new(TextInputMeasure {
88-
height: visible_lines * logical_line_height * target.scale_factor(),
148+
width,
149+
height,
89150
})));
90151
} else {
91-
content_size.measure = None;
152+
content_size.clear();
92153
}
93154
}
94155
}

examples/ui/widgets/feathers_gallery.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use bevy::{
44
color::palettes,
55
feathers::{
6-
constants::icons,
6+
constants::{fonts, icons},
77
containers::{
88
flex_spacer, group, group_body, group_header, pane, pane_body, pane_header,
99
pane_header_divider, subpane, subpane_body, subpane_header,
@@ -19,6 +19,7 @@ use bevy::{
1919
cursor::{EntityCursor, OverrideCursor},
2020
dark_theme::create_dark_theme,
2121
display::{icon, label, label_dim},
22+
font_styles::InheritableFont,
2223
rounded_corners::RoundedCorners,
2324
theme::{ThemeBackgroundColor, ThemedText, UiTheme},
2425
tokens, FeathersPlugins,
@@ -413,13 +414,18 @@ fn demo_column_1() -> impl Scene {
413414
(
414415
:text_input_container
415416
Node {
416-
flex_grow: 1.0,
417+
flex_grow: 0.
418+
padding: { px(4.).left().with_right(px(0.)) },
417419
}
418420
Children [
419421
(
420422
text_input(TextInputProps {
423+
visible_width: Some(10.),
421424
max_characters: Some(9),
422425
})
426+
InheritableFont {
427+
font: fonts::MONO
428+
}
423429
HexColorInput
424430
on(handle_hex_color_change)
425431
)

0 commit comments

Comments
 (0)