From ba12f9e81f044e37d81e54376074cc8aa35e7a72 Mon Sep 17 00:00:00 2001 From: Cristian Dinu Date: Tue, 23 Mar 2021 18:29:20 +0200 Subject: [PATCH] Implement tabs as spaces When we load a string in `TextEdit` we keep the tabs as they are. Now depending on `tab_as_spaces` and `tab_size` properties of the `TextEdit` we have the following situations: - When `tab_as_spaces` is false, then when we press `TAB` a `TAB` char in added to the string. - When `tab_as_spaces` is true, then when we press `TAB` a chunck of `tab_size` spaces is added to the string. To config `tab_as_spaces` and `tab_size` to the values we want we can use the folliwing helper functions: - `TextEdit::tab_as_spaces()` - `TextEdit::tab_size()` Example usage: ```rust ui.add(egui::TextEdit::multiline(&mut self.multiline_text_input) .tab_as_spaces(self.tab_as_spaces) .tab_size(self.tab_size)); ``` Demo: A better demo of this new functionality was implemented in the misc widgets demo What's left to do: - Render actual `TAB` as a rect the size of `tab_size` spaces. - Without default parameters values it seems a pain to implement. I need to think if it is possible to avoid API breaking changes for the font system. From my checks so far I need to pass at least `tab_size` to the fonts system to know how big the glyph for `TAB` will be. --- egui/src/widgets/text_edit.rs | 50 ++++++++++++++++--- egui_demo_lib/src/apps/demo/widget_gallery.rs | 7 --- egui_demo_lib/src/apps/demo/widgets.rs | 32 ++++++++++-- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index 4f949b050..fadf7b209 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -135,6 +135,8 @@ pub struct TextEdit<'t> { enabled: bool, desired_width: Option, desired_height_rows: usize, + tab_as_spaces: bool, + tab_size: usize, } impl<'t> TextEdit<'t> { pub fn cursor(ui: &Ui, id: Id) -> Option { @@ -165,6 +167,8 @@ impl<'t> TextEdit<'t> { enabled: true, desired_width: None, desired_height_rows: 1, + tab_as_spaces: true, + tab_size: 4, } } @@ -182,9 +186,33 @@ impl<'t> TextEdit<'t> { enabled: true, desired_width: None, desired_height_rows: 4, + tab_as_spaces: true, + tab_size: 4, } } + /// Registers if this widget will insert spaces instead of tab char + /// + /// ```rust, ignore + /// ui.add(egui::TextEdit::multiline(&mut self.multiline_text_input) + /// .tab_as_spaces(true)); + /// ``` + pub fn tab_as_spaces(mut self, b: bool) -> Self { + self.tab_as_spaces = b; + self + } + + /// Registers if this widget will insert spaces instead of tab char + /// + /// ```rust, ignore + /// ui.add(egui::TextEdit::multiline(&mut self.multiline_text_input) + /// .tab_size(4)); + /// ``` + pub fn tab_size(mut self, size: usize) -> Self { + self.tab_size = size; + self + } + pub fn id(mut self, id: Id) -> Self { self.id = Some(id); self @@ -297,11 +325,10 @@ impl<'t> TextEdit<'t> { enabled, desired_width, desired_height_rows, + tab_as_spaces, + tab_size, } = self; - // Tabs are represented as 4 spaces for now - *text = text.replace("\t", " "); - let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style); let font = &ui.fonts()[text_style]; let line_spacing = font.row_height(); @@ -453,11 +480,20 @@ impl<'t> TextEdit<'t> { } => { if multiline { let mut ccursor = delete_selected(text, &cursorp); - insert_text(&mut ccursor, text, "\t"); - // Because we add a tab and the tab is represented - // as 4 spaces we must advance the cursor - Some(CCursorPair::one(ccursor + 3)) + if tab_as_spaces { + let mut spaces = String::with_capacity(tab_size); + + for _ in 0..tab_size { + spaces.push(' '); + } + + insert_text(&mut ccursor, text, &spaces); + } else { + insert_text(&mut ccursor, text, "\t"); + } + + Some(CCursorPair::one(ccursor)) } else { None } diff --git a/egui_demo_lib/src/apps/demo/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index 3b4dccde2..997a40041 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -13,7 +13,6 @@ pub struct WidgetGallery { radio: Enum, scalar: f32, string: String, - string_multiline: String, color: egui::Color32, } @@ -25,7 +24,6 @@ impl Default for WidgetGallery { radio: Enum::First, scalar: 42.0, string: Default::default(), - string_multiline: Default::default(), color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5), } } @@ -87,7 +85,6 @@ impl WidgetGallery { radio, scalar, string, - string_multiline, color, } = self; @@ -109,10 +106,6 @@ impl WidgetGallery { ui.add(egui::TextEdit::singleline(string).hint_text("Write something here")); ui.end_row(); - ui.add(doc_link_label("TextEdit", "TextEdit,text_edit")); - ui.add(egui::TextEdit::multiline(string_multiline).hint_text("Write something here")); - ui.end_row(); - ui.add(doc_link_label("Button", "button")); if ui.button("Click me!").clicked() { *boolean = !*boolean; diff --git a/egui_demo_lib/src/apps/demo/widgets.rs b/egui_demo_lib/src/apps/demo/widgets.rs index ed49903db..de3a5fc8a 100644 --- a/egui_demo_lib/src/apps/demo/widgets.rs +++ b/egui_demo_lib/src/apps/demo/widgets.rs @@ -23,6 +23,8 @@ pub struct Widgets { angle: f32, color: Color32, single_line_text_input: String, + tab_size: usize, + tab_as_spaces: bool, multiline_text_input: String, } @@ -35,7 +37,16 @@ impl Default for Widgets { angle: std::f32::consts::TAU / 3.0, color: (Rgba::from_rgb(0.0, 1.0, 0.5) * 0.75).into(), single_line_text_input: "Hello World!".to_owned(), - multiline_text_input: "Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(), + + tab_size: 4, + tab_as_spaces: true, + multiline_text_input: r#"Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs. +This is the start of the next paragraph. + +Existing tabs are kept as tabs. +Use the configs above to set how new tabs should be handled and test by pressing Tab. + +Click me to edit me!"#.to_owned(), } } } @@ -133,7 +144,22 @@ impl Widgets { } }); - ui.label("Multiline text input:"); - ui.text_edit_multiline(&mut self.multiline_text_input); + ui.horizontal(|ui| { + ui.label("Multiline text input:"); + + ui.separator(); + + ui.add(egui::Slider::usize(&mut self.tab_size, 2..=8).text("Tab size")); + + ui.separator(); + + ui.checkbox(&mut self.tab_as_spaces, "Tabs as spaces"); + }); + + ui.add( + egui::TextEdit::multiline(&mut self.multiline_text_input) + .tab_as_spaces(self.tab_as_spaces) + .tab_size(self.tab_size), + ); } }