Add ValueChanged events, with initial support for text.

This commit is contained in:
Nolan Darilek 2021-05-12 13:17:58 -05:00
parent 8269cca95d
commit 97aa56f465
3 changed files with 106 additions and 59 deletions

View file

@ -40,13 +40,10 @@ impl Output {
// only describe last event: // only describe last event:
if let Some(event) = self.events.iter().rev().next() { if let Some(event) = self.events.iter().rev().next() {
match event { match event {
OutputEvent::Clicked(widget_info) => { OutputEvent::Clicked(widget_info)
return widget_info.description(); | OutputEvent::DoubleClicked(widget_info)
} | OutputEvent::FocusGained(widget_info)
OutputEvent::DoubleClicked(widget_info) => { | OutputEvent::ValueChanged(widget_info) => {
return widget_info.description();
}
OutputEvent::FocusGained(widget_info) => {
return widget_info.description(); return widget_info.description();
} }
} }
@ -216,6 +213,8 @@ pub enum OutputEvent {
DoubleClicked(WidgetInfo), DoubleClicked(WidgetInfo),
/// A widget gained keyboard focus (by tab key). /// A widget gained keyboard focus (by tab key).
FocusGained(WidgetInfo), FocusGained(WidgetInfo),
// A widget's value changed.
ValueChanged(WidgetInfo),
} }
impl std::fmt::Debug for OutputEvent { impl std::fmt::Debug for OutputEvent {
@ -224,6 +223,7 @@ impl std::fmt::Debug for OutputEvent {
Self::Clicked(wi) => write!(f, "Clicked({:?})", wi), Self::Clicked(wi) => write!(f, "Clicked({:?})", wi),
Self::DoubleClicked(wi) => write!(f, "DoubleClicked({:?})", wi), Self::DoubleClicked(wi) => write!(f, "DoubleClicked({:?})", wi),
Self::FocusGained(wi) => write!(f, "FocusGained({:?})", wi), Self::FocusGained(wi) => write!(f, "FocusGained({:?})", wi),
Self::ValueChanged(wi) => write!(f, "ValueChanged({:?})", wi),
} }
} }
} }
@ -236,7 +236,9 @@ pub struct WidgetInfo {
/// The text on labels, buttons, checkboxes etc. /// The text on labels, buttons, checkboxes etc.
pub label: Option<String>, pub label: Option<String>,
/// The contents of some editable text (for `TextEdit` fields). /// The contents of some editable text (for `TextEdit` fields).
pub edit_text: Option<String>, pub text_value: Option<String>,
// The previous text value.
prev_text_value: Option<String>,
/// The current value of checkboxes and radio buttons. /// The current value of checkboxes and radio buttons.
pub selected: Option<bool>, pub selected: Option<bool>,
/// The current value of sliders etc. /// The current value of sliders etc.
@ -248,7 +250,8 @@ impl std::fmt::Debug for WidgetInfo {
let Self { let Self {
typ, typ,
label, label,
edit_text, text_value,
prev_text_value,
selected, selected,
value, value,
} = self; } = self;
@ -260,8 +263,11 @@ impl std::fmt::Debug for WidgetInfo {
if let Some(label) = label { if let Some(label) = label {
s.field("label", label); s.field("label", label);
} }
if let Some(edit_text) = edit_text { if let Some(text_value) = text_value {
s.field("edit_text", edit_text); s.field("text_value", text_value);
}
if let Some(prev_text_value) = prev_text_value {
s.field("prev_text_value", prev_text_value);
} }
if let Some(selected) = selected { if let Some(selected) = selected {
s.field("selected", selected); s.field("selected", selected);
@ -279,7 +285,8 @@ impl WidgetInfo {
Self { Self {
typ, typ,
label: None, label: None,
edit_text: None, text_value: None,
prev_text_value: None,
selected: None, selected: None,
value: None, value: None,
} }
@ -321,9 +328,10 @@ impl WidgetInfo {
} }
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
pub fn text_edit(edit_text: impl ToString) -> Self { pub fn text_edit(text_value: impl ToString, prev_text_value: impl ToString) -> Self {
Self { Self {
edit_text: Some(edit_text.to_string()), text_value: Some(text_value.to_string()),
prev_text_value: Some(prev_text_value.to_string()),
..Self::new(WidgetType::TextEdit) ..Self::new(WidgetType::TextEdit)
} }
} }
@ -333,7 +341,8 @@ impl WidgetInfo {
let Self { let Self {
typ, typ,
label, label,
edit_text, text_value,
prev_text_value: _,
selected, selected,
value, value,
} = self; } = self;
@ -370,9 +379,18 @@ impl WidgetInfo {
description = format!("{}: {}", label, description); description = format!("{}: {}", label, description);
} }
if let Some(edit_text) = edit_text { if typ == &WidgetType::TextEdit {
description += " "; let text;
description += edit_text; if let Some(text_value) = text_value {
if text_value.is_empty() {
text = "blank".into();
} else {
text = text_value.to_string();
}
} else {
text = "blank".into();
}
description = format!("{}: {}", text, description);
} }
if let Some(value) = value { if let Some(value) = value {

View file

@ -435,6 +435,8 @@ impl Response {
Some(OutputEvent::DoubleClicked(make_info())) Some(OutputEvent::DoubleClicked(make_info()))
} else if self.gained_focus() { } else if self.gained_focus() {
Some(OutputEvent::FocusGained(make_info())) Some(OutputEvent::FocusGained(make_info()))
} else if self.changed {
Some(OutputEvent::ValueChanged(make_info()))
} else { } else {
None None
}; };

View file

@ -138,6 +138,7 @@ impl CCursorPair {
#[derive(Debug)] #[derive(Debug)]
pub struct TextEdit<'t> { pub struct TextEdit<'t> {
text: &'t mut String, text: &'t mut String,
prev_text: String,
hint_text: String, hint_text: String,
id: Option<Id>, id: Option<Id>,
id_source: Option<Id>, id_source: Option<Id>,
@ -170,6 +171,7 @@ impl<'t> TextEdit<'t> {
pub fn singleline(text: &'t mut String) -> Self { pub fn singleline(text: &'t mut String) -> Self {
TextEdit { TextEdit {
text, text,
prev_text: Default::default(),
hint_text: Default::default(), hint_text: Default::default(),
id: None, id: None,
id_source: None, id_source: None,
@ -189,6 +191,7 @@ impl<'t> TextEdit<'t> {
pub fn multiline(text: &'t mut String) -> Self { pub fn multiline(text: &'t mut String) -> Self {
TextEdit { TextEdit {
text, text,
prev_text: Default::default(),
hint_text: Default::default(), hint_text: Default::default(),
id: None, id: None,
id_source: None, id_source: None,
@ -331,6 +334,7 @@ impl<'t> Widget for TextEdit<'t> {
impl<'t> TextEdit<'t> { impl<'t> TextEdit<'t> {
fn content_ui(self, ui: &mut Ui) -> Response { fn content_ui(self, ui: &mut Ui) -> Response {
let TextEdit { let TextEdit {
mut prev_text,
text, text,
hint_text, hint_text,
id, id,
@ -490,7 +494,11 @@ impl<'t> TextEdit<'t> {
Some(CCursorPair::default()) Some(CCursorPair::default())
} else { } else {
copy_if_not_password(ui, selected_str(text, &cursorp).to_owned()); copy_if_not_password(ui, selected_str(text, &cursorp).to_owned());
Some(CCursorPair::one(delete_selected(text, &cursorp))) Some(CCursorPair::one(delete_selected(
text,
&mut prev_text,
&cursorp,
)))
} }
} }
Event::Text(text_to_insert) => { Event::Text(text_to_insert) => {
@ -499,9 +507,9 @@ impl<'t> TextEdit<'t> {
&& text_to_insert != "\n" && text_to_insert != "\n"
&& text_to_insert != "\r" && text_to_insert != "\r"
{ {
let mut ccursor = delete_selected(text, &cursorp); let mut ccursor = delete_selected(text, &mut prev_text, &cursorp);
insert_text(&mut ccursor, text, text_to_insert); insert_text(&mut ccursor, text, &mut prev_text, text_to_insert);
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} else { } else {
None None
@ -513,12 +521,12 @@ impl<'t> TextEdit<'t> {
modifiers, modifiers,
} => { } => {
if multiline && ui.memory().has_lock_focus(id) { if multiline && ui.memory().has_lock_focus(id) {
let mut ccursor = delete_selected(text, &cursorp); let mut ccursor = delete_selected(text, &mut prev_text, &cursorp);
if modifiers.shift { if modifiers.shift {
// TODO: support removing indentation over a selection? // TODO: support removing indentation over a selection?
decrease_identation(&mut ccursor, text); decrease_identation(&mut ccursor, text);
} else { } else {
insert_text(&mut ccursor, text, "\t"); insert_text(&mut ccursor, text, &mut prev_text, "\t");
} }
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} else { } else {
@ -531,8 +539,8 @@ impl<'t> TextEdit<'t> {
.. ..
} => { } => {
if multiline { if multiline {
let mut ccursor = delete_selected(text, &cursorp); let mut ccursor = delete_selected(text, &mut prev_text, &cursorp);
insert_text(&mut ccursor, text, "\n"); insert_text(&mut ccursor, text, &mut prev_text, "\n");
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} else { } else {
ui.memory().surrender_focus(id); // End input with enter ui.memory().surrender_focus(id); // End input with enter
@ -548,6 +556,7 @@ impl<'t> TextEdit<'t> {
if let Some((undo_ccursorp, undo_txt)) = if let Some((undo_ccursorp, undo_txt)) =
state.undoer.undo(&(cursorp.as_ccursorp(), text.clone())) state.undoer.undo(&(cursorp.as_ccursorp(), text.clone()))
{ {
prev_text = text.clone();
*text = undo_txt.clone(); *text = undo_txt.clone();
Some(*undo_ccursorp) Some(*undo_ccursorp)
} else { } else {
@ -559,7 +568,7 @@ impl<'t> TextEdit<'t> {
key, key,
pressed: true, pressed: true,
modifiers, modifiers,
} => on_key_press(&mut cursorp, text, &galley, *key, modifiers), } => on_key_press(&mut cursorp, text, &mut prev_text, &galley, *key, modifiers),
Event::CompositionStart => { Event::CompositionStart => {
state.has_ime = true; state.has_ime = true;
@ -572,9 +581,9 @@ impl<'t> TextEdit<'t> {
&& text_mark != "\r" && text_mark != "\r"
&& state.has_ime && state.has_ime
{ {
let mut ccursor = delete_selected(text, &cursorp); let mut ccursor = delete_selected(text, &mut prev_text, &cursorp);
let start_cursor = ccursor; let start_cursor = ccursor;
insert_text(&mut ccursor, text, text_mark); insert_text(&mut ccursor, text, &mut prev_text, text_mark);
Some(CCursorPair::two(start_cursor, ccursor)) Some(CCursorPair::two(start_cursor, ccursor))
} else { } else {
None None
@ -588,8 +597,8 @@ impl<'t> TextEdit<'t> {
&& state.has_ime && state.has_ime
{ {
state.has_ime = false; state.has_ime = false;
let mut ccursor = delete_selected(text, &cursorp); let mut ccursor = delete_selected(text, &mut prev_text, &cursorp);
insert_text(&mut ccursor, text, prediction); insert_text(&mut ccursor, text, &mut prev_text, prediction);
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} else { } else {
None None
@ -656,7 +665,7 @@ impl<'t> TextEdit<'t> {
ui.memory().id_data.insert(id, state); ui.memory().id_data.insert(id, state);
response.widget_info(|| WidgetInfo::text_edit(&*text)); response.widget_info(|| WidgetInfo::text_edit(&*text, &*prev_text));
response response
} }
} }
@ -741,7 +750,13 @@ fn byte_index_from_char_index(s: &str, char_index: usize) -> usize {
s.len() s.len()
} }
fn insert_text(ccursor: &mut CCursor, text: &mut String, text_to_insert: &str) { fn insert_text(
ccursor: &mut CCursor,
text: &mut String,
prev_text: &mut String,
text_to_insert: &str,
) {
*prev_text = text.clone();
let mut char_it = text.chars(); let mut char_it = text.chars();
let mut new_text = String::with_capacity(text.len() + text_to_insert.len()); let mut new_text = String::with_capacity(text.len() + text_to_insert.len());
for _ in 0..ccursor.index { for _ in 0..ccursor.index {
@ -756,15 +771,20 @@ fn insert_text(ccursor: &mut CCursor, text: &mut String, text_to_insert: &str) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
fn delete_selected(text: &mut String, cursorp: &CursorPair) -> CCursor { fn delete_selected(text: &mut String, prev_text: &mut String, cursorp: &CursorPair) -> CCursor {
let [min, max] = cursorp.sorted(); let [min, max] = cursorp.sorted();
delete_selected_ccursor_range(text, [min.ccursor, max.ccursor]) delete_selected_ccursor_range(text, prev_text, [min.ccursor, max.ccursor])
} }
fn delete_selected_ccursor_range(text: &mut String, [min, max]: [CCursor; 2]) -> CCursor { fn delete_selected_ccursor_range(
text: &mut String,
prev_text: &mut String,
[min, max]: [CCursor; 2],
) -> CCursor {
let [min, max] = [min.index, max.index]; let [min, max] = [min.index, max.index];
assert!(min <= max); assert!(min <= max);
if min < max { if min < max {
*prev_text = text.clone();
let mut char_it = text.chars(); let mut char_it = text.chars();
let mut new_text = String::with_capacity(text.len()); let mut new_text = String::with_capacity(text.len());
for _ in 0..min { for _ in 0..min {
@ -779,32 +799,37 @@ fn delete_selected_ccursor_range(text: &mut String, [min, max]: [CCursor; 2]) ->
} }
} }
fn delete_previous_char(text: &mut String, ccursor: CCursor) -> CCursor { fn delete_previous_char(text: &mut String, prev_text: &mut String, ccursor: CCursor) -> CCursor {
if ccursor.index > 0 { if ccursor.index > 0 {
let max_ccursor = ccursor; let max_ccursor = ccursor;
let min_ccursor = max_ccursor - 1; let min_ccursor = max_ccursor - 1;
delete_selected_ccursor_range(text, [min_ccursor, max_ccursor]) delete_selected_ccursor_range(text, prev_text, [min_ccursor, max_ccursor])
} else { } else {
ccursor ccursor
} }
} }
fn delete_next_char(text: &mut String, ccursor: CCursor) -> CCursor { fn delete_next_char(text: &mut String, prev_text: &mut String, ccursor: CCursor) -> CCursor {
delete_selected_ccursor_range(text, [ccursor, ccursor + 1]) delete_selected_ccursor_range(text, prev_text, [ccursor, ccursor + 1])
} }
fn delete_previous_word(text: &mut String, max_ccursor: CCursor) -> CCursor { fn delete_previous_word(
text: &mut String,
prev_text: &mut String,
max_ccursor: CCursor,
) -> CCursor {
let min_ccursor = ccursor_previous_word(text, max_ccursor); let min_ccursor = ccursor_previous_word(text, max_ccursor);
delete_selected_ccursor_range(text, [min_ccursor, max_ccursor]) delete_selected_ccursor_range(text, prev_text, [min_ccursor, max_ccursor])
} }
fn delete_next_word(text: &mut String, min_ccursor: CCursor) -> CCursor { fn delete_next_word(text: &mut String, prev_text: &mut String, min_ccursor: CCursor) -> CCursor {
let max_ccursor = ccursor_next_word(text, min_ccursor); let max_ccursor = ccursor_next_word(text, min_ccursor);
delete_selected_ccursor_range(text, [min_ccursor, max_ccursor]) delete_selected_ccursor_range(text, prev_text, [min_ccursor, max_ccursor])
} }
fn delete_paragraph_before_cursor( fn delete_paragraph_before_cursor(
text: &mut String, text: &mut String,
prev_text: &mut String,
galley: &Galley, galley: &Galley,
cursorp: &CursorPair, cursorp: &CursorPair,
) -> CCursor { ) -> CCursor {
@ -815,14 +840,15 @@ fn delete_paragraph_before_cursor(
prefer_next_row: true, prefer_next_row: true,
}); });
if min.ccursor == max.ccursor { if min.ccursor == max.ccursor {
delete_previous_char(text, min.ccursor) delete_previous_char(text, prev_text, min.ccursor)
} else { } else {
delete_selected(text, &CursorPair::two(min, max)) delete_selected(text, prev_text, &CursorPair::two(min, max))
} }
} }
fn delete_paragraph_after_cursor( fn delete_paragraph_after_cursor(
text: &mut String, text: &mut String,
prev_text: &mut String,
galley: &Galley, galley: &Galley,
cursorp: &CursorPair, cursorp: &CursorPair,
) -> CCursor { ) -> CCursor {
@ -833,9 +859,9 @@ fn delete_paragraph_after_cursor(
prefer_next_row: false, prefer_next_row: false,
}); });
if min.ccursor == max.ccursor { if min.ccursor == max.ccursor {
delete_next_char(text, min.ccursor) delete_next_char(text, prev_text, min.ccursor)
} else { } else {
delete_selected(text, &CursorPair::two(min, max)) delete_selected(text, prev_text, &CursorPair::two(min, max))
} }
} }
@ -845,6 +871,7 @@ fn delete_paragraph_after_cursor(
fn on_key_press( fn on_key_press(
cursorp: &mut CursorPair, cursorp: &mut CursorPair,
text: &mut String, text: &mut String,
prev_text: &mut String,
galley: &Galley, galley: &Galley,
key: Key, key: Key,
modifiers: &Modifiers, modifiers: &Modifiers,
@ -852,31 +879,31 @@ fn on_key_press(
match key { match key {
Key::Backspace => { Key::Backspace => {
let ccursor = if modifiers.mac_cmd { let ccursor = if modifiers.mac_cmd {
delete_paragraph_before_cursor(text, galley, cursorp) delete_paragraph_before_cursor(text, prev_text, galley, cursorp)
} else if let Some(cursor) = cursorp.single() { } else if let Some(cursor) = cursorp.single() {
if modifiers.alt || modifiers.ctrl { if modifiers.alt || modifiers.ctrl {
// alt on mac, ctrl on windows // alt on mac, ctrl on windows
delete_previous_word(text, cursor.ccursor) delete_previous_word(text, prev_text, cursor.ccursor)
} else { } else {
delete_previous_char(text, cursor.ccursor) delete_previous_char(text, prev_text, cursor.ccursor)
} }
} else { } else {
delete_selected(text, cursorp) delete_selected(text, prev_text, cursorp)
}; };
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} }
Key::Delete => { Key::Delete => {
let ccursor = if modifiers.mac_cmd { let ccursor = if modifiers.mac_cmd {
delete_paragraph_after_cursor(text, galley, cursorp) delete_paragraph_after_cursor(text, prev_text, galley, cursorp)
} else if let Some(cursor) = cursorp.single() { } else if let Some(cursor) = cursorp.single() {
if modifiers.alt || modifiers.ctrl { if modifiers.alt || modifiers.ctrl {
// alt on mac, ctrl on windows // alt on mac, ctrl on windows
delete_next_word(text, cursor.ccursor) delete_next_word(text, prev_text, cursor.ccursor)
} else { } else {
delete_next_char(text, cursor.ccursor) delete_next_char(text, prev_text, cursor.ccursor)
} }
} else { } else {
delete_selected(text, cursorp) delete_selected(text, prev_text, cursorp)
}; };
let ccursor = CCursor { let ccursor = CCursor {
prefer_next_row: true, prefer_next_row: true,
@ -892,20 +919,20 @@ fn on_key_press(
} }
Key::K if modifiers.ctrl => { Key::K if modifiers.ctrl => {
let ccursor = delete_paragraph_after_cursor(text, galley, cursorp); let ccursor = delete_paragraph_after_cursor(text, prev_text, galley, cursorp);
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} }
Key::U if modifiers.ctrl => { Key::U if modifiers.ctrl => {
let ccursor = delete_paragraph_before_cursor(text, galley, cursorp); let ccursor = delete_paragraph_before_cursor(text, prev_text, galley, cursorp);
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} }
Key::W if modifiers.ctrl => { Key::W if modifiers.ctrl => {
let ccursor = if let Some(cursor) = cursorp.single() { let ccursor = if let Some(cursor) = cursorp.single() {
delete_previous_word(text, cursor.ccursor) delete_previous_word(text, prev_text, cursor.ccursor)
} else { } else {
delete_selected(text, cursorp) delete_selected(text, prev_text, cursorp)
}; };
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} }