diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 0a52d636c..aa4f18d8a 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -622,9 +622,10 @@ impl TextEdit<'_> { first = false; } - // The hint text should be shown left top instead of centered (important for - // multi line text edits) - atoms.push_right(atom.atom_align(Align2::LEFT_TOP)); + // Align the hint text the same as the input text so the hint, the + // cursor, and the typed text all share one alignment. The default + // `align` is `LEFT_TOP`, which keeps multi line text edits unchanged. + atoms.push_right(atom.atom_align(align)); } // Calculate the empty galley, so it can be read later. The available width is diff --git a/crates/egui_kittest/src/app_kind.rs b/crates/egui_kittest/src/app_kind.rs index 942ec4b85..9e92192bb 100644 --- a/crates/egui_kittest/src/app_kind.rs +++ b/crates/egui_kittest/src/app_kind.rs @@ -23,6 +23,10 @@ pub(crate) enum AppKind<'a, State> { } impl AppKind<'_, State> { + // The `Option` is needed when the `eframe` feature is enabled, because the + // `Eframe` variant has no `egui::Response` to return. Without `eframe` the + // wrap is unnecessary, so we silence `clippy::unnecessary_wraps` for that case. + #[cfg_attr(not(feature = "eframe"), expect(clippy::unnecessary_wraps))] pub fn run( &mut self, ui: &mut egui::Ui, diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index ba39a909b..459e2f024 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -713,3 +713,49 @@ fn collapsing_panel_must_not_grow_enclosing_window() { ); } } + +/// The hint text of a `TextEdit` should follow the same alignment as the input +/// text, instead of always being left-top aligned. +/// +/// Regression test for . +#[test] +pub fn textedit_hint_text_should_follow_text_alignment() { + let mut input = String::new(); + + let mut harness = Harness::builder() + .with_size(Vec2::new(200.0, 40.0)) + .build_ui(|ui| { + ui.add( + egui::TextEdit::singleline(&mut input) + .hint_text("Hint") + .desired_width(200.0) + .horizontal_align(egui::Align::Center), + ); + }); + harness.run(); + + let text_edit = harness.get_by_role(accesskit::Role::TextInput); + let edit_rect = text_edit.rect(); + + // Find the hint text shape (the only text shape while the input is empty). + let hint_shape = harness + .output() + .shapes + .iter() + .find_map(|clipped| { + let egui::epaint::Shape::Text(text_shape) = &clipped.shape else { + return None; + }; + (text_shape.galley.text() == "Hint").then_some(text_shape) + }) + .expect("hint text shape should be painted"); + + let hint_center_x = hint_shape.pos.x + hint_shape.galley.size().x / 2.0; + let edit_center_x = edit_rect.center().x; + + assert!( + (hint_center_x - edit_center_x).abs() < 1.0, + "hint text should be centered in the TextEdit: hint_center_x={hint_center_x}, \ + edit_center_x={edit_center_x}, edit_rect={edit_rect:?}", + ); +}