[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 {
|
impl Widgets {
|
||||||
pub fn ui(&mut self, ui: &mut Ui) {
|
pub fn ui(&mut self, ui: &mut Ui) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(label!("Text can have").text_color(srgba(110, 255, 110, 255)));
|
ui.style_mut().item_spacing.x = 0.0;
|
||||||
ui.add(label!("color").text_color(srgba(128, 140, 255, 255)));
|
ui.add(label!("Text can have ").text_color(srgba(110, 255, 110, 255)));
|
||||||
ui.add(label!("and tooltips (hover me)")).tooltip_text(
|
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.",
|
"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.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.add(Slider::f32(&mut self.slider_value, -10.0..=10.0).text("value"));
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("drag this number:");
|
ui.label("drag this number:");
|
||||||
|
@ -411,18 +419,20 @@ impl Widgets {
|
||||||
if ui.add(Button::new("Assign PI")).clicked {
|
if ui.add(Button::new("Assign PI")).clicked {
|
||||||
self.slider_value = std::f32::consts::PI;
|
self.slider_value = std::f32::consts::PI;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(label!("Single line text input:"));
|
ui.add(label!("Single line text input:"));
|
||||||
ui.add(
|
ui.add(
|
||||||
TextEdit::new(&mut self.single_line_text_input)
|
TextEdit::new(&mut self.single_line_text_input)
|
||||||
.multiline(false)
|
.multiline(false)
|
||||||
.id("single line"),
|
.id_source("single line"),
|
||||||
);
|
);
|
||||||
}); // TODO: .tooltip_text("Enter text to edit me")
|
}); // TODO: .tooltip_text("Enter text to edit me")
|
||||||
|
|
||||||
ui.add(label!("Multiline text input:"));
|
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))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pub(crate) window_interaction: Option<window::WindowInteraction>,
|
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,
|
pub(crate) areas: Areas,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,8 +182,6 @@ impl<'a> Slider<'a> {
|
||||||
|
|
||||||
/// Just the text label
|
/// Just the text label
|
||||||
fn text_ui(&mut self, ui: &mut Ui, x_range: RangeInclusive<f32>) {
|
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 = self.text.as_deref().unwrap_or_default();
|
||||||
let label_text = format!("{}: ", label_text);
|
let label_text = format!("{}: ", label_text);
|
||||||
let text_color = self.text_color.unwrap_or_else(|| ui.style().text_color);
|
let text_color = self.text_color.unwrap_or_else(|| ui.style().text_color);
|
||||||
|
@ -193,12 +191,48 @@ impl<'a> Slider<'a> {
|
||||||
.multiline(false)
|
.multiline(false)
|
||||||
.text_color(text_color),
|
.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(
|
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)
|
Label::new(value_text)
|
||||||
.multiline(false)
|
.multiline(false)
|
||||||
.text_color(text_color)
|
.text_color(text_color)
|
||||||
.text_style(TextStyle::Monospace),
|
.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 {
|
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)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub(crate) struct State {
|
pub(crate) struct State {
|
||||||
/// Charctaer based, NOT bytes.
|
/// Character based, NOT bytes.
|
||||||
/// TODO: store as line + row
|
/// TODO: store as line + row
|
||||||
pub cursor: Option<usize>,
|
pub cursor: Option<usize>,
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,11 @@ pub(crate) struct State {
|
||||||
pub struct TextEdit<'t> {
|
pub struct TextEdit<'t> {
|
||||||
text: &'t mut String,
|
text: &'t mut String,
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
|
id_source: Option<Id>,
|
||||||
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the current Ui"
|
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the current Ui"
|
||||||
text_color: Option<Color>,
|
text_color: Option<Color>,
|
||||||
multiline: bool,
|
multiline: bool,
|
||||||
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> TextEdit<'t> {
|
impl<'t> TextEdit<'t> {
|
||||||
|
@ -23,14 +25,21 @@ impl<'t> TextEdit<'t> {
|
||||||
TextEdit {
|
TextEdit {
|
||||||
text,
|
text,
|
||||||
id: None,
|
id: None,
|
||||||
|
id_source: None,
|
||||||
text_style: TextStyle::Body,
|
text_style: TextStyle::Body,
|
||||||
text_color: Default::default(),
|
text_color: None,
|
||||||
multiline: true,
|
multiline: true,
|
||||||
|
enabled: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(mut self, id_source: impl std::hash::Hash) -> Self {
|
pub fn id(mut self, id: Id) -> Self {
|
||||||
self.id = Some(Id::new(id_source));
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +57,12 @@ impl<'t> TextEdit<'t> {
|
||||||
self.multiline = multiline;
|
self.multiline = multiline;
|
||||||
self
|
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> {
|
impl<'t> Widget for TextEdit<'t> {
|
||||||
|
@ -55,12 +70,14 @@ impl<'t> Widget for TextEdit<'t> {
|
||||||
let TextEdit {
|
let TextEdit {
|
||||||
text,
|
text,
|
||||||
id,
|
id,
|
||||||
|
id_source,
|
||||||
text_style,
|
text_style,
|
||||||
text_color,
|
text_color,
|
||||||
multiline,
|
multiline,
|
||||||
|
enabled,
|
||||||
} = self;
|
} = 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();
|
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 desired_size = galley.size.max(vec2(available_width, line_spacing));
|
||||||
let rect = ui.allocate_space(desired_size);
|
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);
|
ui.memory().request_kb_focus(id);
|
||||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
if let Some(mouse_pos) = ui.input().mouse.pos {
|
||||||
state.cursor = Some(galley.char_at(mouse_pos - interact.rect.min).char_idx);
|
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
|
// User clicked somewhere else
|
||||||
ui.memory().surrender_kb_focus(id);
|
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;
|
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());
|
let mut cursor = state.cursor.unwrap_or_else(|| text.chars().count());
|
||||||
cursor = clamp(cursor, 0..=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");
|
insert_text(&mut cursor, text, "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Key {
|
||||||
|
key: Key::Escape,
|
||||||
|
pressed: true,
|
||||||
|
} => {
|
||||||
|
ui.memory().surrender_kb_focus(id);
|
||||||
|
}
|
||||||
Event::Key { key, pressed: true } => {
|
Event::Key { key, pressed: true } => {
|
||||||
on_key_press(&mut cursor, text, *key);
|
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);
|
painter.galley(interact.rect.min, galley, text_style, text_color);
|
||||||
ui.memory().text_edit.insert(id, state);
|
ui.memory().text_edit.insert(id, state);
|
||||||
interact
|
interact
|
||||||
|
|
Loading…
Reference in a new issue