mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 06:10:06 -04:00
Add control of line height and letter spacing (#3302)
* Add `TextFormat::extra_letter_spacing` * Add control of line height * Add to text layout demo * Move the text layout demo to its own window in the demo app * Fix doclink * Better document points vs pixels * Better documentation and code cleanup
This commit is contained in:
@@ -35,7 +35,8 @@ impl Default for Demos {
|
||||
Box::<super::sliders::Sliders>::default(),
|
||||
Box::<super::strip_demo::StripDemo>::default(),
|
||||
Box::<super::table_demo::TableDemo>::default(),
|
||||
Box::<super::text_edit::TextEdit>::default(),
|
||||
Box::<super::text_edit::TextEditDemo>::default(),
|
||||
Box::<super::text_layout::TextLayoutDemo>::default(),
|
||||
Box::<super::widget_gallery::WidgetGallery>::default(),
|
||||
Box::<super::window_options::WindowOptions>::default(),
|
||||
Box::<super::tests::WindowResizeTest>::default(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use egui::{epaint::text::TextWrapping, *};
|
||||
use egui::*;
|
||||
|
||||
/// Showcase some ui code
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
@@ -7,8 +7,6 @@ use egui::{epaint::text::TextWrapping, *};
|
||||
pub struct MiscDemoWindow {
|
||||
num_columns: usize,
|
||||
|
||||
text_break: TextBreakDemo,
|
||||
|
||||
widgets: Widgets,
|
||||
colors: ColorWidgets,
|
||||
custom_collapsing_header: CustomCollapsingHeader,
|
||||
@@ -24,8 +22,6 @@ impl Default for MiscDemoWindow {
|
||||
MiscDemoWindow {
|
||||
num_columns: 2,
|
||||
|
||||
text_break: Default::default(),
|
||||
|
||||
widgets: Default::default(),
|
||||
colors: Default::default(),
|
||||
custom_collapsing_header: Default::default(),
|
||||
@@ -72,8 +68,6 @@ impl View for MiscDemoWindow {
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
text_layout_demo(ui);
|
||||
ui.separator();
|
||||
self.text_break.ui(ui);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file_line!());
|
||||
});
|
||||
@@ -644,63 +638,3 @@ fn text_layout_demo(ui: &mut Ui) {
|
||||
|
||||
ui.label(job);
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct TextBreakDemo {
|
||||
break_anywhere: bool,
|
||||
max_rows: usize,
|
||||
overflow_character: Option<char>,
|
||||
}
|
||||
|
||||
impl Default for TextBreakDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_rows: 1,
|
||||
break_anywhere: true,
|
||||
overflow_character: Some('…'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextBreakDemo {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
let Self {
|
||||
break_anywhere,
|
||||
max_rows,
|
||||
overflow_character,
|
||||
} = self;
|
||||
|
||||
use egui::text::LayoutJob;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(max_rows));
|
||||
ui.label("Max rows");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Line-break:");
|
||||
ui.radio_value(break_anywhere, false, "word boundaries");
|
||||
ui.radio_value(break_anywhere, true, "anywhere");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(overflow_character, None, "None");
|
||||
ui.selectable_value(overflow_character, Some('…'), "…");
|
||||
ui.selectable_value(overflow_character, Some('—'), "—");
|
||||
ui.selectable_value(overflow_character, Some('-'), " - ");
|
||||
ui.label("Overflow character");
|
||||
});
|
||||
|
||||
let mut job =
|
||||
LayoutJob::single_section(crate::LOREM_IPSUM_LONG.to_owned(), TextFormat::default());
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: *max_rows,
|
||||
break_anywhere: *break_anywhere,
|
||||
overflow_character: *overflow_character,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
ui.label(job); // `Label` overrides some of the wrapping settings, e.g. wrap width
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub mod strip_demo;
|
||||
pub mod table_demo;
|
||||
pub mod tests;
|
||||
pub mod text_edit;
|
||||
pub mod text_layout;
|
||||
pub mod toggle_switch;
|
||||
pub mod widget_gallery;
|
||||
pub mod window_options;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/// Showcase [`TextEdit`].
|
||||
/// Showcase [`egui::TextEdit`].
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct TextEdit {
|
||||
pub struct TextEditDemo {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl Default for TextEdit {
|
||||
impl Default for TextEditDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: "Edit this text".to_owned(),
|
||||
@@ -14,7 +14,7 @@ impl Default for TextEdit {
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Demo for TextEdit {
|
||||
impl super::Demo for TextEditDemo {
|
||||
fn name(&self) -> &'static str {
|
||||
"🖹 TextEdit"
|
||||
}
|
||||
@@ -30,7 +30,7 @@ impl super::Demo for TextEdit {
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for TextEdit {
|
||||
impl super::View for TextEditDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let Self { text } = self;
|
||||
|
||||
|
||||
135
crates/egui_demo_lib/src/demo/text_layout.rs
Normal file
135
crates/egui_demo_lib/src/demo/text_layout.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
/// Showcase text layout
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct TextLayoutDemo {
|
||||
break_anywhere: bool,
|
||||
max_rows: usize,
|
||||
overflow_character: Option<char>,
|
||||
extra_letter_spacing_pixels: i32,
|
||||
line_height_pixels: u32,
|
||||
}
|
||||
|
||||
impl Default for TextLayoutDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_rows: 3,
|
||||
break_anywhere: true,
|
||||
overflow_character: Some('…'),
|
||||
extra_letter_spacing_pixels: 0,
|
||||
line_height_pixels: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Demo for TextLayoutDemo {
|
||||
fn name(&self) -> &'static str {
|
||||
"🖹 Text Layout"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.resizable(true)
|
||||
.show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for TextLayoutDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let Self {
|
||||
break_anywhere,
|
||||
max_rows,
|
||||
overflow_character,
|
||||
extra_letter_spacing_pixels,
|
||||
line_height_pixels,
|
||||
} = self;
|
||||
|
||||
use egui::text::LayoutJob;
|
||||
|
||||
let pixels_per_point = ui.ctx().pixels_per_point();
|
||||
let points_per_pixel = 1.0 / pixels_per_point;
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file_line!());
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
egui::Grid::new("TextLayoutDemo")
|
||||
.num_columns(2)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Max rows:");
|
||||
ui.add(egui::DragValue::new(max_rows));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Line-break:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(break_anywhere, false, "word boundaries");
|
||||
ui.radio_value(break_anywhere, true, "anywhere");
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Overflow character:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(overflow_character, None, "None");
|
||||
ui.selectable_value(overflow_character, Some('…'), "…");
|
||||
ui.selectable_value(overflow_character, Some('—'), "—");
|
||||
ui.selectable_value(overflow_character, Some('-'), " - ");
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Extra letter spacing:");
|
||||
ui.add(egui::DragValue::new(extra_letter_spacing_pixels).suffix(" pixels"));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Line height:");
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.selectable_label(*line_height_pixels == 0, "Default")
|
||||
.clicked()
|
||||
{
|
||||
*line_height_pixels = 0;
|
||||
}
|
||||
if ui
|
||||
.selectable_label(*line_height_pixels != 0, "Custom")
|
||||
.clicked()
|
||||
{
|
||||
*line_height_pixels = (pixels_per_point * 20.0).round() as _;
|
||||
}
|
||||
if *line_height_pixels != 0 {
|
||||
ui.add(egui::DragValue::new(line_height_pixels).suffix(" pixels"));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
let extra_letter_spacing = points_per_pixel * *extra_letter_spacing_pixels as f32;
|
||||
let line_height =
|
||||
(*line_height_pixels != 0).then_some(points_per_pixel * *line_height_pixels as f32);
|
||||
|
||||
let mut job = LayoutJob::single_section(
|
||||
crate::LOREM_IPSUM_LONG.to_owned(),
|
||||
egui::TextFormat {
|
||||
extra_letter_spacing,
|
||||
line_height,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
job.wrap = egui::text::TextWrapping {
|
||||
max_rows: *max_rows,
|
||||
break_anywhere: *break_anywhere,
|
||||
overflow_character: *overflow_character,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// NOTE: `Label` overrides some of the wrapping settings, e.g. wrap width
|
||||
ui.label(job);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -188,5 +188,6 @@ fn format_from_style(
|
||||
underline,
|
||||
strikethrough,
|
||||
valign,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user