[slider] click value to edit it with keyboard
This commit is contained in:
parent
1874f238eb
commit
f3bbb210c0
4 changed files with 115 additions and 34 deletions
|
@ -377,9 +377,10 @@ impl Default for Widgets {
|
|||
impl Widgets {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
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 (hover me)")).tooltip_text(
|
||||
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.",
|
||||
);
|
||||
});
|
||||
|
@ -403,6 +404,13 @@ impl Widgets {
|
|||
ui.add(label!("The button has been clicked {} times", self.count));
|
||||
});
|
||||
|
||||
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:");
|
||||
|
@ -411,18 +419,20 @@ impl Widgets {
|
|||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue