Add support for reporting cursor selection changes.

This commit is contained in:
Nolan Darilek 2021-05-18 13:26:20 -05:00
parent 97aa56f465
commit 2433506c92
4 changed files with 61 additions and 3 deletions

View file

@ -193,7 +193,8 @@ impl CtxRef {
drag_released: false, drag_released: false,
is_pointer_button_down_on: false, is_pointer_button_down_on: false,
interact_pointer_pos: None, interact_pointer_pos: None,
changed: false, // must be set by the widget itself changed: false, // must be set by the widget itself
has_widget_info: false, // must be set by the widget itself
}; };
if !enabled || !sense.focusable || !layer_id.allow_interaction() { if !enabled || !sense.focusable || !layer_id.allow_interaction() {

View file

@ -43,6 +43,7 @@ impl Output {
OutputEvent::Clicked(widget_info) OutputEvent::Clicked(widget_info)
| OutputEvent::DoubleClicked(widget_info) | OutputEvent::DoubleClicked(widget_info)
| OutputEvent::FocusGained(widget_info) | OutputEvent::FocusGained(widget_info)
| OutputEvent::TextSelectionChanged(widget_info)
| OutputEvent::ValueChanged(widget_info) => { | OutputEvent::ValueChanged(widget_info) => {
return widget_info.description(); return widget_info.description();
} }
@ -213,6 +214,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),
// Text selection was updated.
TextSelectionChanged(WidgetInfo),
// A widget's value changed. // A widget's value changed.
ValueChanged(WidgetInfo), ValueChanged(WidgetInfo),
} }
@ -223,6 +226,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::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({:?})", wi),
Self::ValueChanged(wi) => write!(f, "ValueChanged({:?})", wi), Self::ValueChanged(wi) => write!(f, "ValueChanged({:?})", wi),
} }
} }
@ -238,11 +242,15 @@ pub struct WidgetInfo {
/// The contents of some editable text (for `TextEdit` fields). /// The contents of some editable text (for `TextEdit` fields).
pub text_value: Option<String>, pub text_value: Option<String>,
// The previous text value. // The previous text value.
prev_text_value: Option<String>, pub 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.
pub value: Option<f64>, pub value: Option<f64>,
// Location of primary cursor.
pub primary_cursor: Option<usize>,
// Location of secondary cursor.
pub secondary_cursor: Option<usize>,
} }
impl std::fmt::Debug for WidgetInfo { impl std::fmt::Debug for WidgetInfo {
@ -254,6 +262,8 @@ impl std::fmt::Debug for WidgetInfo {
prev_text_value, prev_text_value,
selected, selected,
value, value,
primary_cursor,
secondary_cursor,
} = self; } = self;
let mut s = f.debug_struct("WidgetInfo"); let mut s = f.debug_struct("WidgetInfo");
@ -275,6 +285,12 @@ impl std::fmt::Debug for WidgetInfo {
if let Some(value) = value { if let Some(value) = value {
s.field("value", value); s.field("value", value);
} }
if let Some(primary_cursor) = primary_cursor {
s.field("primary_cursor", primary_cursor);
}
if let Some(secondary_cursor) = secondary_cursor {
s.field("secondary_cursor", secondary_cursor);
}
s.finish() s.finish()
} }
@ -289,6 +305,8 @@ impl WidgetInfo {
prev_text_value: None, prev_text_value: None,
selected: None, selected: None,
value: None, value: None,
primary_cursor: None,
secondary_cursor: None,
} }
} }
@ -336,6 +354,20 @@ impl WidgetInfo {
} }
} }
#[allow(clippy::needless_pass_by_value)]
pub fn text_selection_changed(
primary_cursor: usize,
secondary_cursor: usize,
text_value: impl ToString,
) -> Self {
Self {
primary_cursor: Some(primary_cursor),
secondary_cursor: Some(secondary_cursor),
text_value: Some(text_value.to_string()),
..Self::new(WidgetType::TextEdit)
}
}
/// This can be used by a text-to-speech system to describe the widget. /// This can be used by a text-to-speech system to describe the widget.
pub fn description(&self) -> String { pub fn description(&self) -> String {
let Self { let Self {
@ -345,6 +377,8 @@ impl WidgetInfo {
prev_text_value: _, prev_text_value: _,
selected, selected,
value, value,
primary_cursor: _,
secondary_cursor: _,
} = self; } = self;
// TODO: localization // TODO: localization

View file

@ -65,6 +65,8 @@ pub struct Response {
/// e.g. the slider was dragged, text was entered in a `TextEdit` etc. /// e.g. the slider was dragged, text was entered in a `TextEdit` etc.
/// Always `false` for something like a `Button`. /// Always `false` for something like a `Button`.
pub(crate) changed: bool, pub(crate) changed: bool,
/// Has a `WidgetInfo` but isn't covered by some other case (E.g. `changed`, `clicked`.)
pub(crate) has_widget_info: bool,
} }
impl std::fmt::Debug for Response { impl std::fmt::Debug for Response {
@ -84,6 +86,7 @@ impl std::fmt::Debug for Response {
is_pointer_button_down_on, is_pointer_button_down_on,
interact_pointer_pos, interact_pointer_pos,
changed, changed,
has_widget_info,
} = self; } = self;
f.debug_struct("Response") f.debug_struct("Response")
.field("layer_id", layer_id) .field("layer_id", layer_id)
@ -99,6 +102,7 @@ impl std::fmt::Debug for Response {
.field("is_pointer_button_down_on", is_pointer_button_down_on) .field("is_pointer_button_down_on", is_pointer_button_down_on)
.field("interact_pointer_pos", interact_pointer_pos) .field("interact_pointer_pos", interact_pointer_pos)
.field("changed", changed) .field("changed", changed)
.field("has_widget_info", has_widget_info)
.finish() .finish()
} }
} }
@ -437,6 +441,13 @@ impl Response {
Some(OutputEvent::FocusGained(make_info())) Some(OutputEvent::FocusGained(make_info()))
} else if self.changed { } else if self.changed {
Some(OutputEvent::ValueChanged(make_info())) Some(OutputEvent::ValueChanged(make_info()))
} else if self.has_widget_info {
let info = make_info();
if info.primary_cursor.is_some() && info.secondary_cursor.is_some() {
Some(OutputEvent::TextSelectionChanged(info))
} else {
None
}
} else { } else {
None None
}; };
@ -479,6 +490,7 @@ impl Response {
|| other.is_pointer_button_down_on, || other.is_pointer_button_down_on,
interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos), interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos),
changed: self.changed || other.changed, changed: self.changed || other.changed,
has_widget_info: self.has_widget_info || other.has_widget_info,
} }
} }
} }

View file

@ -665,7 +665,18 @@ 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, &*prev_text)); if response.changed {
response.widget_info(|| WidgetInfo::text_edit(&*text, &*prev_text));
} else if let Some(text_cursor) = text_cursor {
response.has_widget_info = true;
response.widget_info(|| {
WidgetInfo::text_selection_changed(
text_cursor.primary.ccursor.index,
text_cursor.secondary.ccursor.index,
&*text,
)
});
}
response response
} }
} }