1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -04:00
Files
egui/crates/egui_kittest/src/app_kind.rs
Davy dcd0c72d53 Fix TextEdit hint text not following horizontal_align/vertical_align (#8332)
## Summary

- Closes #8309
- [x] I have followed the instructions in the PR template

`TextEdit` hint text was always aligned to `Align2::LEFT_TOP`, ignoring
the alignment set via `TextEdit::horizontal_align` / `vertical_align`.
This caused the hint text, the cursor, and the typed text to disagree on
alignment: e.g. a centered `TextEdit` showed a left-aligned hint with a
centered cursor.

The hint text atoms now use the widget's `align`, so the hint matches
the input text alignment. The default `align` is still `LEFT_TOP`, so
multi line text edits (and the default styling) are unchanged.

### Root cause

In `crates/egui/src/widgets/text_edit/builder.rs`, the hint-text branch
hardcoded:

```rust
atoms.push_right(atom.atom_align(Align2::LEFT_TOP));
```

while the input-text branch used `.atom_align(self.align)`. The hint
path now uses `align` as well.

### Drive-by: silence `clippy::unnecessary_wraps` in
`egui_kittest::app_kind`

`AppKind::run` returns `Option<egui::Response>`. The `Option` wrap is
required when the `eframe` feature is enabled (the `Eframe` branch
returns `None`), but `clippy::unnecessary_wraps` fires when
`egui_kittest` is built standalone without the `eframe` feature (e.g.
`cargo clippy -p egui_kittest`). The workspace CI run doesn't hit it
because feature unification via `egui_demo_app` enables `eframe`, but
it's a real annoyance for anyone linting the crate on its own. Added a
scoped `#[cfg_attr(not(feature = "eframe"),
expect(clippy::unnecessary_wraps))]` with an explanatory comment.

## Test plan

- [x] Added `textedit_hint_text_should_follow_text_alignment` kittest
regression in `crates/egui_kittest/tests/regression_tests.rs`. It fails
before the fix (`hint_center_x=24.25` vs `edit_center_x=100`) and passes
after.
- [x] `cargo test -p egui`
- [x] `cargo test -p egui_kittest --all-features --test
regression_tests`
- [x] `cargo clippy -p egui_kittest --all-features --test
regression_tests -- -D warnings`
- [x] `RUSTFLAGS="-D warnings" cargo clippy -p egui_kittest --lib`
(pre-existing `unnecessary_wraps` now silenced)
- [x] `cargo clippy -p egui -- -D warnings`
- [x] `cargo fmt --check`

---------

Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
2026-08-03 08:55:18 +00:00

75 lines
2.7 KiB
Rust

use egui::Frame;
type AppKindUiState<'a, State> = Box<dyn FnMut(&mut egui::Ui, &mut State) + 'a>;
type AppKindUi<'a> = Box<dyn FnMut(&mut egui::Ui) + 'a>;
/// In order to access the [`eframe::App`] trait from the generic `State`, we store a function pointer
/// here that will return the dyn trait from the struct.
/// In the builder we have the correct `where`-clause to be able to create this.
/// Later we can use it anywhere to get the [`eframe::App`] from the `State`.
#[cfg(feature = "eframe")]
pub(crate) struct AppKindEframe<State> {
pub get_app: fn(&mut State) -> &mut dyn eframe::App,
#[cfg(not(target_arch = "wasm32"))]
pub take_app: fn(State) -> Box<dyn eframe::App>,
pub frame: eframe::Frame,
}
pub(crate) enum AppKind<'a, State> {
Ui(AppKindUi<'a>),
UiState(AppKindUiState<'a, State>),
#[cfg(feature = "eframe")]
Eframe(AppKindEframe<State>),
}
impl<State> 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,
state: &mut State,
sizing_pass: bool,
) -> Option<egui::Response> {
match self {
#[cfg(feature = "eframe")]
AppKind::Eframe(AppKindEframe { get_app, frame, .. }) => {
let app = get_app(state);
app.logic(ui, frame);
app.ui(ui, frame);
None
}
kind_ui => Some(kind_ui.run_ui(ui, state, sizing_pass)),
}
}
fn run_ui(
&mut self,
ui: &mut egui::Ui,
state: &mut State,
sizing_pass: bool,
) -> egui::Response {
let mut builder = egui::UiBuilder::new();
if sizing_pass {
builder.sizing_pass = true;
}
ui.scope_builder(builder, |ui| {
Frame::central_panel(ui.style())
// Only set outer margin, so we show no frame for tests with only free-floating windows/popups:
.outer_margin(8.0)
.inner_margin(0.0)
.show(ui, |ui| match self {
AppKind::Ui(f) => f(ui),
AppKind::UiState(f) => f(ui, state),
#[cfg(feature = "eframe")]
AppKind::Eframe(_) => unreachable!(
"run_ui should only be called with AppKind::Ui or AppKind::UiState"
),
});
})
.response
}
}