Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Center-align all text vertically #5117

Merged
merged 19 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};

use emath::Align;
use epaint::{text::FontTweak, Rounding, Shadow, Stroke};

use crate::{
Expand Down Expand Up @@ -196,6 +197,11 @@ pub struct Style {
/// which will take precedence over this.
pub override_font_id: Option<FontId>,

/// How to vertically align text.
///
/// Set to `None` to use align that depends on the current layout.
pub override_text_valign: Option<Align>,

/// The [`FontFamily`] and size you want to use for a specific [`TextStyle`].
///
/// The most convenient way to look something up in this is to use [`TextStyle::resolve`].
Expand Down Expand Up @@ -1201,6 +1207,7 @@ impl Default for Style {
Self {
override_font_id: None,
override_text_style: None,
override_text_valign: Some(Align::Center),
text_styles: default_text_styles(),
drag_value_text_style: TextStyle::Button,
number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
Expand Down Expand Up @@ -1501,6 +1508,7 @@ impl Style {
let Self {
override_font_id,
override_text_style,
override_text_valign,
text_styles,
drag_value_text_style,
number_formatter: _, // can't change callbacks in the UI
Expand Down Expand Up @@ -1534,7 +1542,7 @@ impl Style {
ui.end_row();

ui.label("Override text style");
crate::ComboBox::from_id_salt("Override text style")
crate::ComboBox::from_id_salt("override_text_style")
.selected_text(match override_text_style {
None => "None".to_owned(),
Some(override_text_style) => override_text_style.to_string(),
Expand All @@ -1550,6 +1558,28 @@ impl Style {
});
ui.end_row();

fn valign_name(valign: Align) -> &'static str {
match valign {
Align::TOP => "Top",
Align::Center => "Center",
Align::BOTTOM => "Bottom",
}
}

ui.label("Override text valign");
crate::ComboBox::from_id_salt("override_text_valign")
.selected_text(match override_text_valign {
None => "None",
Some(override_text_valign) => valign_name(*override_text_valign),
})
.show_ui(ui, |ui| {
ui.selectable_value(override_text_valign, None, "None");
for align in [Align::TOP, Align::Center, Align::BOTTOM] {
ui.selectable_value(override_text_valign, Some(align), valign_name(align));
}
});
ui.end_row();

ui.label("Text style of DragValue");
crate::ComboBox::from_id_salt("drag_value_text_style")
.selected_text(drag_value_text_style.to_string())
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/text_selection/accesskit_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub fn update_accesskit_for_text_widget(
value.push(glyph.chr);
character_lengths.push((value.len() - old_len) as _);
character_positions.push(glyph.pos.x - row.rect.min.x);
character_widths.push(glyph.size.x);
character_widths.push(glyph.advance_width);
}

if row.ends_with_newline {
Expand Down
8 changes: 8 additions & 0 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,14 @@ impl Ui {
self.wrap_mode() == TextWrapMode::Wrap
}

/// How to vertically align text
#[inline]
pub fn text_valign(&self) -> Align {
self.style()
.override_text_valign
.unwrap_or_else(|| self.layout().vertical_align())
}

/// Create a painter for a sub-region of this Ui.
///
/// The clip-rect of the returned [`Painter`] will be the intersection
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/widget_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ impl WidgetText {
available_width: f32,
fallback_font: impl Into<FontSelection>,
) -> Arc<Galley> {
let valign = ui.layout().vertical_align();
let valign = ui.text_valign();
let style = ui.style();

let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
Expand Down
6 changes: 3 additions & 3 deletions crates/egui/src/widgets/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,9 @@ impl Widget for Button<'_> {
if image.is_some() && galley.is_some() {
desired_size.x += ui.spacing().icon_spacing;
}
if let Some(text) = &galley {
desired_size.x += text.size().x;
desired_size.y = desired_size.y.max(text.size().y);
if let Some(galley) = &galley {
desired_size.x += galley.size().x;
desired_size.y = desired_size.y.max(galley.size().y);
}
if let Some(shortcut_galley) = &shortcut_galley {
desired_size.x += gap_before_shortcut_text + shortcut_galley.size().x;
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/widgets/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ impl Label {
return (pos, galley, response);
}

let valign = ui.layout().vertical_align();
let valign = ui.text_valign();
let mut layout_job = self
.text
.into_layout_job(ui.style(), FontSelection::Default, valign);
Expand Down
13 changes: 11 additions & 2 deletions crates/egui_demo_lib/src/demo/misc_demo_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,17 @@ fn text_layout_demo(ui: &mut Ui) {
};

job.append(
"This is a demonstration of ",
"This",
first_row_indentation,
TextFormat {
color: default_color,
font_id: FontId::proportional(20.0),
..Default::default()
},
);
job.append(
" is a demonstration of ",
0.0,
TextFormat {
color: default_color,
..Default::default()
Expand Down Expand Up @@ -632,7 +641,7 @@ fn text_layout_demo(ui: &mut Ui) {
"mixing ",
0.0,
TextFormat {
font_id: FontId::proportional(17.0),
font_id: FontId::proportional(20.0),
color: default_color,
..Default::default()
},
Expand Down
8 changes: 8 additions & 0 deletions crates/epaint/src/text/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,14 @@ impl Font {
(Some(font_impl), glyph_info)
}

pub(crate) fn ascent(&self) -> f32 {
if let Some(first) = self.fonts.first() {
first.ascent()
} else {
self.row_height
}
}

fn glyph_info_no_cache_or_fallback(&mut self, c: char) -> Option<(FontIndex, GlyphInfo)> {
for (font_index, font_impl) in self.fonts.iter().enumerate() {
if let Some(glyph_info) = font_impl.glyph_info(c) {
Expand Down
19 changes: 8 additions & 11 deletions crates/epaint/src/text/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl Default for FontTweak {
scale: 1.0,
y_offset_factor: 0.0,
y_offset: 0.0,
baseline_offset_factor: -0.0333, // makes the default fonts look more centered in buttons and such
baseline_offset_factor: 0.0,
}
}
}
Expand Down Expand Up @@ -271,29 +271,26 @@ impl Default for FontDefinitions {
let mut families = BTreeMap::new();

font_data.insert("Hack".to_owned(), FontData::from_static(HACK_REGULAR));
font_data.insert(
"Ubuntu-Light".to_owned(),
FontData::from_static(UBUNTU_LIGHT),
);

// Some good looking emojis. Use as first priority:
font_data.insert(
"NotoEmoji-Regular".to_owned(),
FontData::from_static(NOTO_EMOJI_REGULAR).tweak(FontTweak {
scale: 0.81, // make it smaller
scale: 0.81, // Make smaller
..Default::default()
}),
);

font_data.insert(
"Ubuntu-Light".to_owned(),
FontData::from_static(UBUNTU_LIGHT),
);

// Bigger emojis, and more. <http://jslegers.github.io/emoji-icon-font/>:
font_data.insert(
"emoji-icon-font".to_owned(),
FontData::from_static(EMOJI_ICON).tweak(FontTweak {
scale: 0.88, // make it smaller

// probably not correct, but this does make texts look better (#2724 for details)
y_offset_factor: 0.11, // move glyphs down to better align with common fonts
baseline_offset_factor: -0.11, // ...now the entire row is a bit down so shift it back
scale: 0.90, // Make smaller
..Default::default()
}),
);
Expand Down
75 changes: 43 additions & 32 deletions crates/epaint/src/text/text_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,12 @@ fn layout_section(
paragraph.glyphs.push(Glyph {
chr,
pos: pos2(paragraph.cursor_x, f32::NAN),
size: vec2(glyph_info.advance_width, line_height),
ascent: font_impl.map_or(0.0, |font| font.ascent()), // Failure to find the font here would be weird
advance_width: glyph_info.advance_width,
line_height,
font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
font_height: font.row_height(),
font_ascent: font.ascent(),
uv_rect: glyph_info.uv_rect,
section_index,
});
Expand Down Expand Up @@ -376,7 +380,7 @@ fn replace_last_glyph_with_overflow_character(

let (_, last_glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);

let mut x = last_glyph.pos.x + last_glyph.size.x;
let mut x = last_glyph.pos.x + last_glyph.advance_width;

let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character);

Expand All @@ -391,8 +395,12 @@ fn replace_last_glyph_with_overflow_character(
row.glyphs.push(Glyph {
chr: overflow_character,
pos: pos2(x, f32::NAN),
size: vec2(replacement_glyph_info.advance_width, line_height),
ascent: font_impl.map_or(0.0, |font| font.ascent()), // Failure to find the font here would be weird
advance_width: replacement_glyph_info.advance_width,
line_height,
font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
font_height: font.row_height(),
font_ascent: font.ascent(),
uv_rect: replacement_glyph_info.uv_rect,
section_index,
});
Expand All @@ -409,8 +417,12 @@ fn replace_last_glyph_with_overflow_character(
row.glyphs.push(Glyph {
chr: overflow_character,
pos: pos2(x, f32::NAN),
size: vec2(replacement_glyph_info.advance_width, line_height),
ascent: font_impl.map_or(0.0, |font| font.ascent()), // Failure to find the font here would be weird
advance_width: replacement_glyph_info.advance_width,
line_height,
font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
font_height: font.row_height(),
font_ascent: font.ascent(),
uv_rect: replacement_glyph_info.uv_rect,
section_index,
});
Expand Down Expand Up @@ -438,7 +450,6 @@ fn replace_last_glyph_with_overflow_character(
let section = &job.sections[last_glyph.section_index as usize];
let extra_letter_spacing = section.format.extra_letter_spacing;
let font = fonts.font(&section.format.font_id);
let line_height = row_height(section, font);

if let Some(prev_glyph) = prev_glyph {
let prev_glyph_id = font.font_impl_and_glyph_info(prev_glyph.chr).1.id;
Expand All @@ -453,7 +464,9 @@ fn replace_last_glyph_with_overflow_character(
// Replace the glyph:
last_glyph.chr = overflow_character;
let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
last_glyph.size = vec2(glyph_info.advance_width, line_height);
last_glyph.advance_width = glyph_info.advance_width;
last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent());
last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height());
last_glyph.uv_rect = glyph_info.uv_rect;

// Reapply kerning:
Expand All @@ -472,8 +485,10 @@ fn replace_last_glyph_with_overflow_character(
} else {
// Just replace and be done with it.
last_glyph.chr = overflow_character;
let (_, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
last_glyph.size = vec2(glyph_info.advance_width, line_height);
let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
last_glyph.advance_width = glyph_info.advance_width;
last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent());
last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height());
last_glyph.uv_rect = glyph_info.uv_rect;
return;
}
Expand Down Expand Up @@ -585,40 +600,36 @@ fn galley_from_rows(
let mut min_x: f32 = 0.0;
let mut max_x: f32 = 0.0;
for row in &mut rows {
let mut line_height = first_row_min_height.max(row.rect.height());
let mut row_ascent = 0.0f32;
let mut max_row_height = first_row_min_height.max(row.rect.height());
first_row_min_height = 0.0;

// take metrics from the highest font in this row
if let Some(glyph) = row
.glyphs
.iter()
.max_by(|a, b| a.size.y.partial_cmp(&b.size.y).unwrap())
{
line_height = glyph.size.y;
row_ascent = glyph.ascent;
for glyph in &row.glyphs {
max_row_height = max_row_height.max(glyph.line_height);
}
line_height = point_scale.round_to_pixel(line_height);
max_row_height = point_scale.round_to_pixel(max_row_height);

// Now positions each glyph:
// Now position each glyph vertically:
for glyph in &mut row.glyphs {
let format = &job.sections[glyph.section_index as usize].format;

let align_offset = match format.valign {
Align::Center | Align::Max => row_ascent,
glyph.pos.y = cursor_y
+ glyph.font_impl_ascent

// Apply valign to the different in height of the entire row, and the height of this `Font`:
+ format.valign.to_factor() * (max_row_height - glyph.line_height)

// When mixing different `FontImpl` (e.g. latin and emojis),
// we always center the difference:
+ 0.5 * (glyph.font_height - glyph.font_impl_height);

// raised text.
Align::Min => glyph.ascent,
};
glyph.pos.y = cursor_y + align_offset;
glyph.pos.y = point_scale.round_to_pixel(glyph.pos.y);
}

row.rect.min.y = cursor_y;
row.rect.max.y = cursor_y + line_height;
row.rect.max.y = cursor_y + max_row_height;

min_x = min_x.min(row.rect.min.x);
max_x = max_x.max(row.rect.max.x);
cursor_y += line_height;
cursor_y += max_row_height;
cursor_y = point_scale.round_to_pixel(cursor_y);
}

Expand Down
Loading
Loading