1use core::hash::BuildHasher;
2use core::time::Duration;
3
4use crate::{ComputedNode, ComputedUiRenderTargetInfo, ContentSize, NodeMeasure};
5use bevy_asset::Assets;
6
7use bevy_ecs::{
8 change_detection::{DetectChanges, DetectChangesMut},
9 component::Component,
10 entity::Entity,
11 system::{Local, Query, Res, ResMut},
12 world::Ref,
13};
14use bevy_image::prelude::*;
15use bevy_input_focus::InputFocus;
16use bevy_math::{Rect, Vec2};
17use bevy_platform::hash::FixedHasher;
18
19use bevy_text::{
20 add_glyph_to_atlas, cursor_reveal_rect, get_glyph_atlas_info, scrollable_text_layout_width,
21 EditableText, EditableTextGeneration, Font, FontAtlasKey, FontAtlasSet, FontCx, FontHinting,
22 FontSize, GlyphCacheKey, LayoutCx, LineBreak, LineHeight, PositionedGlyph, RemSize,
23 RunGeometry, ScaleCx, TextBrush, TextFont, TextLayout, TextLayoutInfo, TextLineYBounds,
24};
25use bevy_time::{Real, Time};
26use parley::{BoundingBox, PositionedLayoutItem, StyleProperty};
27use smallvec::SmallVec;
28use swash::FontRef;
29use taffy::MaybeMath;
30
31struct TextInputMeasure {
32 width: Option<f32>,
33 height: Option<f32>,
34}
35
36#[derive(impl bevy_ecs::component::Component for EditableTextContentSizeState where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::Table;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {}
fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
use bevy_ecs::component::{
DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
};
(&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
}
fn relationship_accessor()
->
::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
::core::option::Option::None
}
}Component, #[automatically_derived]impl ::core::default::Default for
EditableTextContentSizeState {
#[inline]
fn default() -> Self {
EditableTextContentSizeState {
visible_width: ::core::default::Default::default(),
visible_lines: ::core::default::Default::default(),
}
}
}Default)]
38pub struct EditableTextContentSizeState {
39 visible_width: Option<f32>,
40 visible_lines: Option<f32>,
41}
42
43impl crate::Measure for TextInputMeasure {
44 fn measure(&mut self, measure_args: crate::MeasureArgs<'_>) -> Vec2 {
45 let width = measure_args.resolve_width();
46 let height = measure_args.resolve_height();
47
48 let x = width
49 .effective
50 .unwrap_or(self.width.unwrap_or(match measure_args.available_width {
51 crate::AvailableSpace::Definite(x) => x,
52 crate::AvailableSpace::MinContent | crate::AvailableSpace::MaxContent => 0.0,
53 }))
54 .maybe_clamp(width.min, width.max);
55 let y = height
56 .effective
57 .unwrap_or(self.height.unwrap_or(match measure_args.available_height {
58 crate::AvailableSpace::Definite(y) => y,
59 crate::AvailableSpace::MinContent | crate::AvailableSpace::MaxContent => 0.0,
60 }))
61 .maybe_clamp(height.min, height.max);
62
63 Vec2::new(x, y).ceil()
64 }
65}
66
67fn query_family<'a, 'b>(
68 family: &'a parley::FontFamilyName<'b>,
69) -> parley::fontique::QueryFamily<'a> {
70 match family {
71 parley::FontFamilyName::Named(name) => parley::fontique::QueryFamily::Named(name.as_ref()),
72 parley::FontFamilyName::Generic(generic) => {
73 parley::fontique::QueryFamily::Generic(*generic)
74 }
75 }
76}
77
78pub fn update_editable_text_content_size(
82 mut text_input_query: Query<(
83 &EditableText,
84 Ref<TextFont>,
85 Ref<LineHeight>,
86 Ref<ComputedUiRenderTargetInfo>,
87 &mut ContentSize,
88 &mut EditableTextContentSizeState,
89 )>,
90 fonts: Res<Assets<Font>>,
91 mut font_cx: ResMut<FontCx>,
92 rem_size: Res<RemSize>,
93) {
94 for (editable_text, text_font, line_height, target, mut content_size, mut size_state) in
95 &mut text_input_query
96 {
97 let sizing_changed = size_state.visible_width != editable_text.visible_width
98 || size_state.visible_lines != editable_text.visible_lines;
99 if !(sizing_changed
100 || text_font.is_changed()
101 || line_height.is_changed()
102 || target.is_changed()
103 || rem_size.is_changed())
104 {
105 continue;
106 }
107
108 size_state.visible_width = editable_text.visible_width;
109 size_state.visible_lines = editable_text.visible_lines;
110
111 let font_size = text_font.font_size.eval(target.logical_size(), *rem_size);
112
113 let width = editable_text.visible_width.and_then(|visible_width| {
114 let font_context = &mut font_cx.context;
115 let mut query = font_context
116 .collection
117 .query(&mut font_context.source_cache);
118
119 match text_font.font.resolve_font_family(fonts.as_ref()).ok()? {
120 parley::FontFamily::Source(source) => {
121 query.set_families(
122 parley::FontFamilyName::parse_css_list(&source)
123 .map_while(Result::ok)
124 .collect::<SmallVec<[_; 4]>>()
125 .iter()
126 .map(query_family),
127 );
128 }
129 parley::FontFamily::Single(family) => {
130 query.set_families([query_family(&family)]);
131 }
132 parley::FontFamily::List(families) => {
133 query.set_families(families.iter().map(query_family));
134 }
135 }
136 query.set_attributes(parley::fontique::Attributes::new(
137 text_font.width.into(),
138 text_font.style.into(),
139 text_font.weight.into(),
140 ));
141
142 let mut width = None;
143 query.matches_with(|query_font| {
144 let Some((glyph_id, font_ref)) = query_font
145 .charmap()
146 .and_then(|char_map| char_map.map('0'))
147 .and_then(|glyph_id| u16::try_from(glyph_id).ok())
148 .zip(FontRef::from_index(
149 query_font.blob.as_ref(),
150 query_font.index as usize,
151 ))
152 else {
153 return parley::fontique::QueryStatus::Continue;
154 };
155
156 let advance = font_ref
157 .glyph_metrics(&[])
158 .scale(font_size)
159 .advance_width(glyph_id);
160 if advance.is_finite() {
161 width = Some(advance.max(0.0));
162 parley::fontique::QueryStatus::Stop
163 } else {
164 parley::fontique::QueryStatus::Continue
165 }
166 });
167
168 width.map(|width| width * visible_width * target.scale_factor())
169 });
170
171 let height = editable_text.visible_lines.map(|visible_lines| {
172 let logical_line_height = match *line_height {
173 LineHeight::Px(px) => px,
174 LineHeight::RelativeToFont(scale) => scale * font_size,
175 };
176 visible_lines * logical_line_height * target.scale_factor()
177 });
178
179 if width.is_some() || height.is_some() {
180 content_size.set(NodeMeasure::Custom(Box::new(TextInputMeasure {
181 width,
182 height,
183 })));
184 } else {
185 content_size.clear();
186 }
187 }
188}
189
190pub fn update_editable_text_styles(
193 fonts: Res<Assets<Font>>,
194 mut editable_text_query: Query<(
195 &mut EditableText,
196 Ref<TextFont>,
197 Ref<LineHeight>,
198 Ref<ComputedUiRenderTargetInfo>,
199 Ref<TextLayout>,
200 )>,
201 rem_size: Res<RemSize>,
202) {
203 for (mut editable_text, text_font, line_height, target, text_layout) in
204 editable_text_query.iter_mut()
205 {
206 if f32::EPSILON < (target.scale_factor() - editable_text.editor.get_scale()).abs() {
207 editable_text.editor.set_scale(target.scale_factor());
208 }
209
210 if text_font.is_changed()
211 || #[allow(non_exhaustive_omitted_patterns)] match text_font.font_size {
FontSize::Rem(_) => true,
_ => false,
}matches!(text_font.font_size, FontSize::Rem(_)) && rem_size.is_changed()
212 || #[allow(non_exhaustive_omitted_patterns)] match text_font.font_size {
FontSize::Vw(_) | FontSize::Vh(_) | FontSize::VMin(_) | FontSize::VMax(_)
=> true,
_ => false,
}matches!(
213 text_font.font_size,
214 FontSize::Vw(_) | FontSize::Vh(_) | FontSize::VMin(_) | FontSize::VMax(_)
215 ) && target.is_changed()
216 {
217 editable_text
218 .editor
219 .edit_styles()
220 .insert(StyleProperty::FontSize(
221 text_font.font_size.eval(target.logical_size(), *rem_size),
222 ));
223 }
224
225 if text_font.is_changed()
226 && let Ok(resolved_family) = text_font.font.resolve_font_family(fonts.as_ref())
227 {
228 let family = resolved_family.into_owned();
229 let style_set = editable_text.editor.edit_styles();
230 style_set.insert(StyleProperty::FontFamily(family));
231 }
232
233 if text_font.is_changed() {
234 let style_set = editable_text.editor.edit_styles();
235 style_set.insert(StyleProperty::FontWeight(text_font.weight.into()));
236 style_set.insert(StyleProperty::FontWidth(text_font.width.into()));
237 style_set.insert(StyleProperty::FontStyle(text_font.style.into()));
238 style_set.insert(StyleProperty::FontFeatures(
239 (&text_font.font_features).into(),
240 ));
241 style_set.insert(StyleProperty::FontVariations(
242 (&text_font.font_variations).into(),
243 ));
244 style_set.insert(StyleProperty::Brush(TextBrush::new(
245 0,
246 text_font.font_smoothing,
247 )));
248 }
249
250 if line_height.is_changed() {
251 let style_set = editable_text.editor.edit_styles();
252 style_set.insert(StyleProperty::LineHeight(line_height.eval()));
253 }
254
255 if text_layout.is_changed() {
256 let style_set = editable_text.editor.edit_styles();
257 match text_layout.linebreak {
258 LineBreak::AnyCharacter => {
259 style_set.insert(StyleProperty::WordBreak(parley::WordBreak::BreakAll));
260 style_set.insert(StyleProperty::OverflowWrap(parley::OverflowWrap::Normal));
261 style_set.insert(StyleProperty::TextWrapMode(parley::TextWrapMode::Wrap));
262 }
263 LineBreak::WordOrCharacter => {
264 style_set.insert(StyleProperty::WordBreak(parley::WordBreak::Normal));
265 style_set.insert(StyleProperty::OverflowWrap(parley::OverflowWrap::Anywhere));
266 style_set.insert(StyleProperty::TextWrapMode(parley::TextWrapMode::Wrap));
267 }
268 LineBreak::NoWrap => {
269 style_set.insert(StyleProperty::WordBreak(parley::WordBreak::Normal));
270 style_set.insert(StyleProperty::OverflowWrap(parley::OverflowWrap::Normal));
271 style_set.insert(StyleProperty::TextWrapMode(parley::TextWrapMode::NoWrap));
272 }
273 LineBreak::WordBoundary => {
274 style_set.insert(StyleProperty::WordBreak(parley::WordBreak::Normal));
275 style_set.insert(StyleProperty::OverflowWrap(parley::OverflowWrap::Normal));
276 style_set.insert(StyleProperty::TextWrapMode(parley::TextWrapMode::Wrap));
277 }
278 }
279
280 editable_text
281 .editor
282 .set_alignment(text_layout.justify.into());
283 }
284 }
285}
286
287pub fn sync_editable_text_viewports(mut query: Query<(&mut EditableText, &ComputedNode)>) {
289 for (mut editable_text, computed_node) in &mut query {
290 let size = computed_node.content_box().size();
291 if editable_text.viewport.size != size {
292 editable_text.viewport.size = size;
293 editable_text.editor.set_width(Some(size.x));
294 }
295 }
296}
297
298pub fn update_editable_text_layout(
302 mut font_cx: ResMut<FontCx>,
303 mut layout_cx: ResMut<LayoutCx>,
304 mut scale_cx: ResMut<ScaleCx>,
305 mut font_atlas_set: ResMut<FontAtlasSet>,
306 mut textures: ResMut<Assets<Image>>,
307 mut input_field_query: Query<(
308 Entity,
309 &TextFont,
310 &TextLayout,
311 Ref<FontHinting>,
312 Ref<ComputedUiRenderTargetInfo>,
313 &mut EditableText,
314 &mut TextLayoutInfo,
315 &mut EditableTextGeneration,
316 )>,
317 rem_size: Res<RemSize>,
318 input_focus: Option<Res<InputFocus>>,
319 mut cursor_timer: Local<Duration>,
320 mut previous_focus: Local<Option<Entity>>,
321 time: Res<Time<Real>>,
322) {
323 *cursor_timer += time.delta();
324 let current_focus = input_focus
325 .as_ref()
326 .and_then(|input_focus| input_focus.get());
327 let focus_changed = *previous_focus != current_focus;
328
329 for (
330 entity,
331 text_font,
332 text_layout,
333 hinting,
334 target,
335 mut editable_text,
336 mut info,
337 mut generation,
338 ) in input_field_query.iter_mut()
339 {
340 let cursor_width = editable_text.cursor_width;
341 let cursor_blink_period = editable_text.cursor_blink_period;
342 let cursor_margin = editable_text.cursor_margin;
343 let viewport_before = editable_text.viewport;
344
345 let inner = editable_text.bypass_change_detection();
349 let (editor, viewport) = (&mut inner.editor, &mut inner.viewport);
350
351 let mut driver = editor.driver(font_cx.as_mut(), layout_cx.as_mut());
352
353 driver.refresh_layout();
354
355 let compose_range = driver.editor.raw_compose().clone();
356
357 let layout_changed = driver.editor.generation() != **generation;
358 if layout_changed {
359 **generation = driver.editor.generation();
360 }
361
362 if layout_changed || hinting.is_changed() {
363 let layout = driver.layout();
364
365 info.scale_factor = layout.scale();
366 info.size = (layout.full_width(), layout.height()).into();
367
368 info.preedit_underline_rects.clear();
369 info.glyphs.clear();
370 info.run_geometry.clear();
371
372 for (line_index, line) in layout.lines().enumerate() {
373 for item in line.items() {
374 match item {
375 PositionedLayoutItem::GlyphRun(glyph_run) => {
376 let brush = glyph_run.style().brush;
377
378 let run = glyph_run.run();
379
380 let font_data = run.font();
381 let font_size = run.font_size();
382 let coords = run.normalized_coords();
383
384 let font_atlas_key = FontAtlasKey {
385 id: font_data.data.id() as u32,
386 index: font_data.index,
387 font_size_bits: font_size.to_bits(),
388 variations_hash: FixedHasher.hash_one(coords),
389 hinting: *hinting,
390 font_smoothing: brush.font_smoothing,
391 };
392
393 for glyph in glyph_run.positioned_glyphs() {
394 let font_atlases =
395 font_atlas_set.entry(font_atlas_key).or_default();
396 let Ok(atlas_info) = get_glyph_atlas_info(
397 font_atlases,
398 GlyphCacheKey {
399 glyph_id: glyph.id as u16,
400 },
401 )
402 .map(Ok)
403 .unwrap_or_else(|| {
404 let font_ref = FontRef::from_index(
405 font_data.data.as_ref(),
406 font_data.index as usize,
407 )
408 .unwrap();
409 let font_id = [font_data.data.id(), font_data.index.into()];
410
411 let mut scaler = scale_cx
412 .builder_with_id(font_ref, font_id)
413 .size(font_size)
414 .hint(#[allow(non_exhaustive_omitted_patterns)] match *hinting {
FontHinting::Enabled => true,
_ => false,
}matches!(*hinting, FontHinting::Enabled))
415 .normalized_coords(coords)
416 .build();
417 add_glyph_to_atlas(
418 font_atlases,
419 textures.as_mut(),
420 &mut scaler,
421 text_font.font_smoothing,
422 glyph.id as u16,
423 )
424 }) else {
425 continue;
426 };
427
428 info.glyphs.push(PositionedGlyph {
429 position: Vec2::new(glyph.x, glyph.y)
430 + atlas_info.rect.size() / 2.
431 + atlas_info.offset,
432 atlas_info,
433 section_index: brush.section_index,
434 line_index: line_index as u32,
435 });
436 }
437
438 let metrics = run.metrics();
439 let underline_y = glyph_run.baseline() - metrics.underline_offset;
440 let underline_thickness = metrics.underline_size;
441
442 let run_text_range = run.text_range();
443 if let Some(cr) = &compose_range
444 && run_text_range.start < cr.end
445 && run_text_range.end > cr.start
446 {
447 let mut x = glyph_run.offset();
448 let mut underline_start_x = None;
449 let mut underline_end_x = x;
450
451 for cluster in run.visual_clusters() {
452 let ct = cluster.text_range();
453 if ct.start < cr.end && ct.end > cr.start {
454 underline_start_x.get_or_insert(x);
455 underline_end_x = x + cluster.advance();
456 }
457 x += cluster.advance();
458 }
459
460 if let Some(start_x) = underline_start_x {
461 info.preedit_underline_rects.push(Rect {
462 min: Vec2::new(start_x, underline_y),
463 max: Vec2::new(
464 underline_end_x,
465 underline_y + underline_thickness,
466 ),
467 });
468 }
469 }
470
471 info.run_geometry.push(RunGeometry {
472 section_index: brush.section_index,
473 bounds: Rect {
474 min: Vec2::new(
475 glyph_run.offset(),
476 line.metrics().block_min_coord,
477 ),
478 max: Vec2::new(
479 glyph_run.offset() + glyph_run.advance(),
480 line.metrics().block_max_coord,
481 ),
482 },
483 strikethrough_y: glyph_run.baseline()
484 - metrics.strikethrough_offset,
485 strikethrough_thickness: metrics.strikethrough_size,
486 underline_y,
487 underline_thickness,
488 });
489 }
490 PositionedLayoutItem::InlineBox(_inline) => {
491 }
493 }
494 }
495 }
496
497 info.selection_rects = driver
498 .editor
499 .selection_geometry()
500 .iter()
501 .map(|&b| bounding_box_to_rect(b.0))
502 .collect();
503
504 for i in 0..info.selection_rects.len().saturating_sub(1) {
505 let [a, b] = &mut info.selection_rects[i..i + 2] else {
506 ::core::panicking::unreachable_default_msg();unreachable!();
507 };
508 if a.max.y < b.min.y {
509 a.max.y = b.min.y;
510 }
511 }
512 }
513
514 let full_layout_size = {
515 let layout = driver.layout();
516 Vec2::new(layout.full_width(), layout.height())
517 };
518 let is_focused = Some(entity) == current_focus;
519 let cursor_reveal = is_focused
520 .then(|| cursor_reveal_rect(&mut driver))
521 .flatten();
522
523 if is_focused {
524 if focus_changed || layout_changed || *cursor_timer >= cursor_blink_period {
525 *cursor_timer = Duration::ZERO;
526 }
527
528 info.cursor = driver
529 .editor
530 .cursor_geometry(
531 cursor_width * text_font.font_size.eval(target.logical_size(), *rem_size),
532 )
533 .map(bounding_box_to_rect)
534 .map(|rect| (*cursor_timer < cursor_blink_period / 2, rect));
535 } else {
536 info.cursor = driver
537 .editor
538 .cursor_geometry(0.)
539 .map(bounding_box_to_rect)
540 .map(|rect| (false, rect));
541 }
542
543 if focus_changed && let Some(cursor) = cursor_reveal {
544 let line_bounds = driver
545 .layout()
546 .lines()
547 .map(|line| TextLineYBounds::from_line(&line));
548 viewport.reveal_caret(cursor, full_layout_size, cursor_margin, line_bounds);
549 }
550 let viewport_width = viewport.size.x;
551 viewport.clamp_inside(Vec2::new(
552 scrollable_text_layout_width(
553 text_layout.linebreak,
554 full_layout_size.x,
555 viewport_width,
556 cursor_reveal,
557 ),
558 full_layout_size.y,
559 ));
560
561 if layout_changed || *viewport != viewport_before {
564 editable_text.set_changed();
565 }
566 }
567
568 *previous_focus = current_focus;
569}
570
571fn bounding_box_to_rect(geom: BoundingBox) -> Rect {
572 Rect {
573 min: Vec2 {
574 x: geom.x0 as f32,
575 y: geom.y0 as f32,
576 },
577 max: Vec2 {
578 x: geom.x1 as f32,
579 y: geom.y1 as f32,
580 },
581 }
582}