diff --git a/egui/src/demos/app.rs b/egui/src/demos/app.rs index 1d1f7f63..f62a88a5 100644 --- a/egui/src/demos/app.rs +++ b/egui/src/demos/app.rs @@ -377,12 +377,13 @@ impl Default for Widgets { impl Widgets { pub fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { - ui.add(label!("Text can have").text_color(srgba(110, 255, 110, 255))); - ui.add(label!("color").text_color(srgba(128, 140, 255, 255))); - ui.add(label!("and tooltips (hover me)")).tooltip_text( - "This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.", - ); - }); + ui.style_mut().item_spacing.x = 0.0; + ui.add(label!("Text can have ").text_color(srgba(110, 255, 110, 255))); + ui.add(label!("color ").text_color(srgba(128, 140, 255, 255))); + ui.add(label!("and tooltips")).tooltip_text( + "This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.", + ); + }); ui.horizontal(|ui| { ui.radio_value("First", &mut self.radio, 0); @@ -403,26 +404,35 @@ impl Widgets { ui.add(label!("The button has been clicked {} times", self.count)); }); - ui.add(Slider::f32(&mut self.slider_value, -10.0..=10.0).text("value")); - ui.horizontal(|ui| { - ui.label("drag this number:"); - ui.add(DragValue::f32(&mut self.slider_value).speed(0.01)); - }); - if ui.add(Button::new("Assign PI")).clicked { - self.slider_value = std::f32::consts::PI; + ui.separator(); + { + ui.label( + "The slider will show as many decimals as needed, \ + and will intelligently help you select a round number when you interact with it.\n\ + You can click a slider value to edit it with the keyboard.", + ); + ui.add(Slider::f32(&mut self.slider_value, -10.0..=10.0).text("value")); + ui.horizontal(|ui| { + ui.label("drag this number:"); + ui.add(DragValue::f32(&mut self.slider_value).speed(0.01)); + }); + if ui.add(Button::new("Assign PI")).clicked { + self.slider_value = std::f32::consts::PI; + } } + ui.separator(); ui.horizontal(|ui| { ui.add(label!("Single line text input:")); ui.add( TextEdit::new(&mut self.single_line_text_input) .multiline(false) - .id("single line"), + .id_source("single line"), ); }); // TODO: .tooltip_text("Enter text to edit me") ui.add(label!("Multiline text input:")); - ui.add(TextEdit::new(&mut self.multiline_text_input).id("multiline")); + ui.add(TextEdit::new(&mut self.multiline_text_input).id_source("multiline")); } } diff --git a/egui/src/memory.rs b/egui/src/memory.rs index d2db8e3c..9619ae78 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -28,6 +28,11 @@ pub struct Memory { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) window_interaction: Option, + /// For temporary edit of e.g. a slider value. + /// Couples with `kb_focus_id`. + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) temp_edit_string: Option, + pub(crate) areas: Areas, } diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index 8ce9da5c..042bf814 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -182,8 +182,6 @@ impl<'a> Slider<'a> { /// Just the text label fn text_ui(&mut self, ui: &mut Ui, x_range: RangeInclusive) { - let aim_radius = ui.input().aim_radius(); - let value_text = self.format_value(aim_radius, x_range); let label_text = self.text.as_deref().unwrap_or_default(); let label_text = format!("{}: ", label_text); let text_color = self.text_color.unwrap_or_else(|| ui.style().text_color); @@ -193,12 +191,48 @@ impl<'a> Slider<'a> { .multiline(false) .text_color(text_color), ); - ui.add( - Label::new(value_text) - .multiline(false) - .text_color(text_color) - .text_style(TextStyle::Monospace), - ); + + let edit_id = self.id.expect("We should have an id by now").with("edit"); + let is_editing = ui.memory().has_kb_focus(edit_id); + + let aim_radius = ui.input().aim_radius(); + let mut value_text = self.format_value(aim_radius, x_range); + + if is_editing { + value_text = ui + .memory() + .temp_edit_string + .take() + .unwrap_or_else(|| value_text); + ui.add( + TextEdit::new(&mut value_text) + .id(edit_id) + .multiline(false) + .text_color(text_color) + .text_style(TextStyle::Monospace), + ); + if let Ok(value) = value_text.parse() { + self.set_value_f32(value); + } + if ui.input().key_pressed(Key::Enter) { + ui.memory().surrender_kb_focus(edit_id); + } else { + ui.memory().temp_edit_string = Some(value_text); + } + } else { + let mut response = ui.add( + Label::new(value_text) + .multiline(false) + .text_color(text_color) + .text_style(TextStyle::Monospace), + ); + response.tooltip_text("Click to edit"); + let response = ui.interact(response.rect, edit_id, Sense::click()); + if response.clicked { + ui.memory().request_kb_focus(edit_id); + ui.memory().temp_edit_string = None; // Filled in next frame + } + } } fn format_value(&mut self, aim_radius: f32, x_range: RangeInclusive) -> String { diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index 70c7abf3..7516ac89 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -3,7 +3,7 @@ use crate::{paint::*, *}; #[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct State { - /// Charctaer based, NOT bytes. + /// Character based, NOT bytes. /// TODO: store as line + row pub cursor: Option, } @@ -13,9 +13,11 @@ pub(crate) struct State { pub struct TextEdit<'t> { text: &'t mut String, id: Option, + id_source: Option, text_style: TextStyle, // TODO: Option, where None means "use the default for the current Ui" text_color: Option, multiline: bool, + enabled: bool, } impl<'t> TextEdit<'t> { @@ -23,14 +25,21 @@ impl<'t> TextEdit<'t> { TextEdit { text, id: None, + id_source: None, text_style: TextStyle::Body, - text_color: Default::default(), + text_color: None, multiline: true, + enabled: true, } } - pub fn id(mut self, id_source: impl std::hash::Hash) -> Self { - self.id = Some(Id::new(id_source)); + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + + pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self { + self.id_source = Some(Id::new(id_source)); self } @@ -48,6 +57,12 @@ impl<'t> TextEdit<'t> { self.multiline = multiline; self } + + /// Default is `true`. If set to `false` then you cannot edit the text. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } } impl<'t> Widget for TextEdit<'t> { @@ -55,12 +70,14 @@ impl<'t> Widget for TextEdit<'t> { let TextEdit { text, id, + id_source, text_style, text_color, multiline, + enabled, } = self; - let id = ui.make_child_id(id); + let id = id.unwrap_or_else(|| ui.make_child_id(id_source)); let mut state = ui.memory().text_edit.get(&id).cloned().unwrap_or_default(); @@ -74,23 +91,31 @@ impl<'t> Widget for TextEdit<'t> { }; let desired_size = galley.size.max(vec2(available_width, line_spacing)); let rect = ui.allocate_space(desired_size); - let interact = ui.interact(rect, id, Sense::click_and_drag()); // TODO: implement drag-select + let sense = if enabled { + Sense::click_and_drag() + } else { + Sense::nothing() + }; + let interact = ui.interact(rect, id, sense); // TODO: implement drag-select - if interact.clicked { + if interact.clicked && enabled { ui.memory().request_kb_focus(id); if let Some(mouse_pos) = ui.input().mouse.pos { state.cursor = Some(galley.char_at(mouse_pos - interact.rect.min).char_idx); } - } else if ui.input().mouse.click { + } else if ui.input().mouse.click || (ui.input().mouse.pressed && !interact.hovered) { // User clicked somewhere else ui.memory().surrender_kb_focus(id); } + if !enabled { + ui.memory().surrender_kb_focus(id); + } - if interact.hovered { + if interact.hovered && enabled { ui.output().cursor_icon = CursorIcon::Text; } - if ui.memory().has_kb_focus(id) { + if ui.memory().has_kb_focus(id) && enabled { let mut cursor = state.cursor.unwrap_or_else(|| text.chars().count()); cursor = clamp(cursor, 0..=text.chars().count()); @@ -114,6 +139,12 @@ impl<'t> Widget for TextEdit<'t> { insert_text(&mut cursor, text, "\n"); } } + Event::Key { + key: Key::Escape, + pressed: true, + } => { + ui.memory().surrender_kb_focus(id); + } Event::Key { key, pressed: true } => { on_key_press(&mut cursor, text, *key); } @@ -166,7 +197,8 @@ impl<'t> Widget for TextEdit<'t> { } } - let text_color = text_color.unwrap_or_else(|| ui.style().text_color); + let text_color = + text_color.unwrap_or_else(|| ui.style().interact.style(&interact).stroke_color); painter.galley(interact.rect.min, galley, text_style, text_color); ui.memory().text_edit.insert(id, state); interact