ComboBox, DragValue, and Button not aligned properly in a horizontal layout
🌟Problem Summary I'm facing a layout issue while using egui for building a UI.
Inside the CentralPanel, I used ui.horizontal(|ui| { ... }) to lay out several components in a single row, including:
Multiple ComboBoxes
Multiple DragValue inputs
Some Buttons
These components are not properly aligned in a single horizontal row — they appear at different vertical positions and look visually unbalanced, especially when the font size is large.
✅What I want to achieve I want all these widgets to be aligned neatly in a single row, with consistent height and baseline alignment, so they appear clean and well-organized.
`[package] name = "test0" version = "0.1.0" edition = "2024"
[dependencies] eframe = "0.32.0" egui = "0.32.0" `
` use eframe::{egui, Result};
fn fontsize_setting(ctx: &egui::Context, size: f32) { let mut style = (*ctx.style()).clone();
style.text_styles = [
(egui::TextStyle::Heading, egui::FontId::new(size, egui::FontFamily::Proportional)),
(egui::TextStyle::Body, egui::FontId::new(size, egui::FontFamily::Proportional)),
(egui::TextStyle::Monospace, egui::FontId::new(size, egui::FontFamily::Monospace)),
(egui::TextStyle::Button, egui::FontId::new(size, egui::FontFamily::Proportional)),
(egui::TextStyle::Small, egui::FontId::new(size, egui::FontFamily::Proportional)),
].into();
ctx.set_style(style);
}
struct MyApp{ n_vec: Vec<String>, n_name: String, n_min: u32, }
impl Default for MyApp{ fn default() -> Self{ Self{ n_vec: vec!["".to_string()], n_name: "".to_string(), n_min: 0, } } }
impl eframe::App for MyApp{ fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fontsize_setting(ctx, 30.0);
egui::CentralPanel::default().show(ctx, |ui|{
ui.horizontal(|ui|{
egui::ComboBox::from_id_salt("C_1")
.selected_text(&self.n_name)
.show_ui(ui, |ui|{
for i in &self.n_vec{
ui.selectable_value(&mut self.n_name, i.clone(), i);
}
});
ui.add(|ui: &mut egui::Ui|{
let response = ui.button("+").on_hover_text("C_1_b");
if response.clicked() {
println!("+")
}
response
});
egui::ComboBox::from_id_salt("C_2")
.selected_text(&self.n_name)
.show_ui(ui, |ui|{
for i in &self.n_vec{
ui.selectable_value(&mut self.n_name, i.clone(), i);
}
});
ui.add(|ui: &mut egui::Ui|{
let response = ui.button("+").on_hover_text("C_2_b");
if response.clicked() {
println!("+")
}
response
});
ui.add(
egui::DragValue::new(&mut self.n_min).range(0..=10000)
).on_hover_text("C_2_d");
egui::ComboBox::from_id_salt("C_3")
.selected_text(&self.n_name)
.show_ui(ui, |ui|{
for i in &self.n_vec{
ui.selectable_value(&mut self.n_name, i.clone(), i);
}
});
ui.add(
egui::DragValue::new(&mut self.n_min).range(0..=10000)
).on_hover_text("C_3_d");
egui::ComboBox::from_id_salt("C_4")
.selected_text(&self.n_name)
.show_ui(ui, |ui|{
for i in &self.n_vec{
ui.selectable_value(&mut self.n_name, i.clone(), i);
}
});
ui.add(
egui::DragValue::new(&mut self.n_min).range(0..=10000)
).on_hover_text("C_4_d");
egui::ComboBox::from_id_salt("C_5")
.selected_text(&self.n_name)
.show_ui(ui, |ui|{
for i in &self.n_vec{
ui.selectable_value(&mut self.n_name, i.clone(), i);
}
});
ui.add(
egui::DragValue::new(&mut self.n_min).range(0..=10000)
).on_hover_text("C_5_d");
});
});
}
}
fn main() -> Result<(), eframe::Error> { let option = eframe::NativeOptions::default(); eframe::run_native("test", option, Box::new(|_cc| Ok( Box::new(MyApp::default()) ))) }
`
The issue persists in v0.33.0. Any update on this?
The vertical drift is an issue with using Ui::horizontal() without allocating some amount of space:
ui.horizontal(|ui| {
ui.allocate_space([0., 100.].into());
. . .
});
Normally allocating sufficient space resolves the alignment issues, but in your case the result would be similar to using Ui::horizontal_centered():
The misaligned ComboBoxes are due to your use of TextStyles, as removing them prevents the both the misalignment and vertical drift:
Using Ui::horizontal() without an allocation is usually better served by Ui::horizontal_top(), which in your case produces the result I presume you were expecting:
All that said, I think this still demonstrates a bug in the combination of a centered ComboBox and custom Style::text_styles (at least the ones you are using).
How can I align images and comboboxes? Currently, the comboboxes are not centered and the images shift upwards, whether I use ui.horizontal or horizontal_centered, with and without ui.allocate_space([0., 100.].into());
You should compare the image alignment to a button/label as the combo box is not centered correctly. Outside of manual layout, I don't think there's a way to get the combo box working without a fix.
If you're willing to fork, you can use the below workaround. I will stress that this is just a bandage, but hopefully it points towards the root cause.
ComboBox alignment workaround
diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs
index 8195024f..21cbf28d 100644
--- a/crates/egui/src/containers/combo_box.rs
+++ b/crates/egui/src/containers/combo_box.rs
@@ -235,6 +235,7 @@ impl ComboBox {
let button_id = ui.make_persistent_id(id_salt);
+ let parent_horizontal_aligned = (ui.layout().is_horizontal(), ui.layout().cross_align);
ui.horizontal(|ui| {
let mut ir = combo_box_dyn(
ui,
@@ -246,6 +247,7 @@ impl ComboBox {
close_behavior,
popup_style,
(width, height),
+ parent_horizontal_aligned,
);
if let Some(label) = label {
ir.response.widget_info(|| {
@@ -329,6 +331,7 @@ fn combo_box_dyn<'c, R>(
close_behavior: Option<PopupCloseBehavior>,
popup_style: StyleModifier,
(width, height): (Option<f32>, Option<f32>),
+ parent_horizontal_aligned: (bool, crate::Align),
) -> InnerResponse<Option<R>> {
let popup_id = ComboBox::widget_to_popup_id(button_id);
@@ -339,7 +342,7 @@ fn combo_box_dyn<'c, R>(
let close_behavior = close_behavior.unwrap_or(PopupCloseBehavior::CloseOnClick);
let margin = ui.spacing().button_padding;
- let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
+ let button_response = button_frame(ui, button_id, parent_horizontal_aligned, is_popup_open, Sense::click(), |ui| {
let icon_spacing = ui.spacing().icon_spacing;
let icon_size = Vec2::splat(ui.spacing().icon_width);
@@ -426,6 +429,7 @@ fn combo_box_dyn<'c, R>(
fn button_frame(
ui: &mut Ui,
id: Id,
+ parent_horizontal_aligned: (bool, crate::Align),
is_popup_open: bool,
sense: Sense,
add_contents: impl FnOnce(&mut Ui),
@@ -437,6 +441,17 @@ fn button_frame(
let mut outer_rect = ui.available_rect_before_wrap();
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
+ let row_height = ui.text_style_height(&TextStyle::Button);
+ if row_height > crate::FontId::default().size && parent_horizontal_aligned.0 {
+ let adjustment = match parent_horizontal_aligned.1 {
+ crate::Align::Min => 0.,
+ crate::Align::Center => f32::midpoint(row_height - interact_size.y, 2. * margin.y),
+ crate::Align::Max => row_height - interact_size.y + 2. * margin.y,
+ };
+ let mut center = outer_rect.center();
+ center.y -= adjustment;
+ outer_rect.set_center(center);
+ }
let inner_rect = outer_rect.shrink2(margin);
let mut content_ui = ui.new_child(UiBuilder::new().max_rect(inner_rect));
I am also experiencing this issue using ui.horizontal_wrapped and generating egui::Frames in a loop. I was able to work around it by removing the .inner_margin() and .outer_margin() on the frame, and instead wrapped my content within the frame in a ui.horizontal_centered and a ui.vertical_centered in order to put padding on the left and right.
(This snippet won't run without the rest of my code, I just wanted to include a code example of the workaround)
ui.horizontal_wrapped(|ui| {
let block_min_size = egui::Vec2::new(200.0, 250.0);
for option in options {
let is_selected = *current_selection == option;
let frame_inner_response = ui.allocate_ui_with_layout(
block_min_size,
egui::Layout::top_down(egui::Align::LEFT),
|block_ui| {
egui::Frame::default()
.fill(if is_selected { Color32::from_rgb(40, 60, 80) } else { block_ui.visuals().panel_fill })
.stroke(Stroke::new(1.5, Color32::from_rgb(55,55,55)))
.corner_radius(egui::CornerRadius::same(5))
// .outer_margin(5) // <- Commenting out these lines works around the issue
// .inner_margin(5)
.show(block_ui, |ui| {
// Enforce the size inside the frame
ui.set_min_size(block_min_size);
ui.set_max_size(block_min_size);
// This `horizontal_centered` is to add padding whilst working around the bug.
ui.horizontal_centered(|ui| {
ui.add_space(5.);
ui.vertical_centered(|ui| {
ui.add_space(5.0);
option.show_block(ui); // Function to draw most of the content in the box, not shown here.
ui.label(egui::RichText::new(option.name()).strong().size(14.0));
ui.add_space(5.0);
});
ui.add_space(3.);
});
});
}
);
}
});
Bug present:
Bug worked around: