[slider] click value to edit it with keyboard

This commit is contained in:
Emil Ernerfeldt 2020-08-28 10:37:44 +02:00
parent 1874f238eb
commit f3bbb210c0
4 changed files with 115 additions and 34 deletions

View file

@ -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"));
}
}

View file

@ -28,6 +28,11 @@ pub struct Memory {
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) window_interaction: Option<window::WindowInteraction>,
/// 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<String>,
pub(crate) areas: Areas,
}

View file

@ -182,8 +182,6 @@ impl<'a> Slider<'a> {
/// Just the text label
fn text_ui(&mut self, ui: &mut Ui, x_range: RangeInclusive<f32>) {
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<f32>) -> String {

View file

@ -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<usize>,
}
@ -13,9 +13,11 @@ pub(crate) struct State {
pub struct TextEdit<'t> {
text: &'t mut String,
id: Option<Id>,
id_source: Option<Id>,
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the current Ui"
text_color: Option<Color>,
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