Bug fix: arrow keys won't move focus away from TextEdit (#3352)
* Bug fix: arrow keys won't move focus away from TextEdit * Fix using arrow-keys to move slider * Typo
This commit is contained in:
parent
9c4f55b1f4
commit
ceb8723c5e
|
|
@ -1276,7 +1276,7 @@ impl Context {
|
||||||
nodes,
|
nodes,
|
||||||
tree: Some(accesskit::Tree::new(root_id)),
|
tree: Some(accesskit::Tree::new(root_id)),
|
||||||
focus: has_focus.then(|| {
|
focus: has_focus.then(|| {
|
||||||
let focus_id = self.memory(|mem| mem.interaction.focus.id);
|
let focus_id = self.memory(|mem| mem.focus());
|
||||||
focus_id.map_or(root_id, |id| id.accesskit_id())
|
focus_id.map_or(root_id, |id| id.accesskit_id())
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1028,3 +1028,62 @@ impl From<u32> for TouchId {
|
||||||
Self(id as u64)
|
Self(id as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// TODO(emilk): generalize this to a proper event filter.
|
||||||
|
/// Controls which events that a focused widget will have exclusive access to.
|
||||||
|
///
|
||||||
|
/// Currently this only controls a few special keyboard events,
|
||||||
|
/// but in the future this `struct` should be extended into a full callback thing.
|
||||||
|
///
|
||||||
|
/// Any events not covered by the filter are given to the widget, but are not exclusive.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct EventFilter {
|
||||||
|
/// If `true`, pressing tab will act on the widget,
|
||||||
|
/// and NOT move focus away from the focused widget.
|
||||||
|
///
|
||||||
|
/// Default: `false`
|
||||||
|
pub tab: bool,
|
||||||
|
|
||||||
|
/// If `true`, pressing arrows will act on the widget,
|
||||||
|
/// and NOT move focus away from the focused widget.
|
||||||
|
///
|
||||||
|
/// Default: `false`
|
||||||
|
pub arrows: bool,
|
||||||
|
|
||||||
|
/// If `true`, pressing escape will act on the widget,
|
||||||
|
/// and NOT surrender focus from the focused widget.
|
||||||
|
///
|
||||||
|
/// Default: `false`
|
||||||
|
pub escape: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::derivable_impls)] // let's be explicit
|
||||||
|
impl Default for EventFilter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
tab: false,
|
||||||
|
arrows: false,
|
||||||
|
escape: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventFilter {
|
||||||
|
pub fn matches(&self, event: &Event) -> bool {
|
||||||
|
if let Event::Key { key, .. } = event {
|
||||||
|
match key {
|
||||||
|
crate::Key::Tab => self.tab,
|
||||||
|
crate::Key::ArrowUp
|
||||||
|
| crate::Key::ArrowRight
|
||||||
|
| crate::Key::ArrowDown
|
||||||
|
| crate::Key::ArrowLeft => self.arrows,
|
||||||
|
crate::Key::Escape => self.escape,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -461,6 +461,15 @@ impl InputState {
|
||||||
pub fn num_accesskit_action_requests(&self, id: crate::Id, action: accesskit::Action) -> usize {
|
pub fn num_accesskit_action_requests(&self, id: crate::Id, action: accesskit::Action) -> usize {
|
||||||
self.accesskit_action_requests(id, action).count()
|
self.accesskit_action_requests(id, action).count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all events that matches the given filter.
|
||||||
|
pub fn filtered_events(&self, filter: &EventFilter) -> Vec<Event> {
|
||||||
|
self.events
|
||||||
|
.iter()
|
||||||
|
.filter(|event| filter.matches(event))
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use epaint::{emath::Rangef, vec2, Vec2};
|
use epaint::{emath::Rangef, vec2, Vec2};
|
||||||
|
|
||||||
use crate::{area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style};
|
use crate::{area, window, EventFilter, Id, IdMap, InputState, LayerId, Pos2, Rect, Style};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -219,7 +219,7 @@ pub(crate) struct Interaction {
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub(crate) struct Focus {
|
pub(crate) struct Focus {
|
||||||
/// The widget with keyboard focus (i.e. a text input field).
|
/// The widget with keyboard focus (i.e. a text input field).
|
||||||
pub(crate) id: Option<Id>,
|
focused_widget: Option<FocusWidget>,
|
||||||
|
|
||||||
/// What had keyboard focus previous frame?
|
/// What had keyboard focus previous frame?
|
||||||
id_previous_frame: Option<Id>,
|
id_previous_frame: Option<Id>,
|
||||||
|
|
@ -237,9 +237,6 @@ pub(crate) struct Focus {
|
||||||
/// The last widget interested in focus.
|
/// The last widget interested in focus.
|
||||||
last_interested: Option<Id>,
|
last_interested: Option<Id>,
|
||||||
|
|
||||||
/// If `true`, pressing tab will NOT move focus away from the current widget.
|
|
||||||
is_focus_locked: bool,
|
|
||||||
|
|
||||||
/// Set when looking for widget with navigational keys like arrows, tab, shift+tab
|
/// Set when looking for widget with navigational keys like arrows, tab, shift+tab
|
||||||
focus_direction: FocusDirection,
|
focus_direction: FocusDirection,
|
||||||
|
|
||||||
|
|
@ -247,6 +244,22 @@ pub(crate) struct Focus {
|
||||||
focus_widgets_cache: IdMap<Rect>,
|
focus_widgets_cache: IdMap<Rect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The widget with focus.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct FocusWidget {
|
||||||
|
pub id: Id,
|
||||||
|
pub filter: EventFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusWidget {
|
||||||
|
pub fn new(id: Id) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
filter: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Interaction {
|
impl Interaction {
|
||||||
/// Are we currently clicking or dragging an egui widget?
|
/// Are we currently clicking or dragging an egui widget?
|
||||||
pub fn is_using_pointer(&self) -> bool {
|
pub fn is_using_pointer(&self) -> bool {
|
||||||
|
|
@ -278,14 +291,15 @@ impl Interaction {
|
||||||
impl Focus {
|
impl Focus {
|
||||||
/// Which widget currently has keyboard focus?
|
/// Which widget currently has keyboard focus?
|
||||||
pub fn focused(&self) -> Option<Id> {
|
pub fn focused(&self) -> Option<Id> {
|
||||||
self.id
|
self.focused_widget.as_ref().map(|w| w.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin_frame(&mut self, new_input: &crate::data::input::RawInput) {
|
fn begin_frame(&mut self, new_input: &crate::data::input::RawInput) {
|
||||||
self.id_previous_frame = self.id;
|
self.id_previous_frame = self.focused();
|
||||||
if let Some(id) = self.id_next_frame.take() {
|
if let Some(id) = self.id_next_frame.take() {
|
||||||
self.id = Some(id);
|
self.focused_widget = Some(FocusWidget::new(id));
|
||||||
}
|
}
|
||||||
|
let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default();
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
{
|
{
|
||||||
|
|
@ -295,37 +309,35 @@ impl Focus {
|
||||||
self.focus_direction = FocusDirection::None;
|
self.focus_direction = FocusDirection::None;
|
||||||
|
|
||||||
for event in &new_input.events {
|
for event in &new_input.events {
|
||||||
if let crate::Event::Key {
|
if !event_filter.matches(event) {
|
||||||
key,
|
if let crate::Event::Key {
|
||||||
pressed: true,
|
key,
|
||||||
modifiers,
|
pressed: true,
|
||||||
..
|
modifiers,
|
||||||
} = event
|
..
|
||||||
{
|
} = event
|
||||||
if let Some(cardinality) = match key {
|
{
|
||||||
crate::Key::ArrowUp => Some(FocusDirection::Up),
|
if let Some(cardinality) = match key {
|
||||||
crate::Key::ArrowRight => Some(FocusDirection::Right),
|
crate::Key::ArrowUp => Some(FocusDirection::Up),
|
||||||
crate::Key::ArrowDown => Some(FocusDirection::Down),
|
crate::Key::ArrowRight => Some(FocusDirection::Right),
|
||||||
crate::Key::ArrowLeft => Some(FocusDirection::Left),
|
crate::Key::ArrowDown => Some(FocusDirection::Down),
|
||||||
crate::Key::Tab => {
|
crate::Key::ArrowLeft => Some(FocusDirection::Left),
|
||||||
if !self.is_focus_locked {
|
|
||||||
|
crate::Key::Tab => {
|
||||||
if modifiers.shift {
|
if modifiers.shift {
|
||||||
Some(FocusDirection::Previous)
|
Some(FocusDirection::Previous)
|
||||||
} else {
|
} else {
|
||||||
Some(FocusDirection::Next)
|
Some(FocusDirection::Next)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
crate::Key::Escape => {
|
||||||
|
self.focused_widget = None;
|
||||||
|
Some(FocusDirection::None)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
} {
|
||||||
|
self.focus_direction = cardinality;
|
||||||
}
|
}
|
||||||
crate::Key::Escape => {
|
|
||||||
self.id = None;
|
|
||||||
self.is_focus_locked = false;
|
|
||||||
Some(FocusDirection::None)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
} {
|
|
||||||
self.focus_direction = cardinality;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,17 +358,17 @@ impl Focus {
|
||||||
pub(crate) fn end_frame(&mut self, used_ids: &IdMap<Rect>) {
|
pub(crate) fn end_frame(&mut self, used_ids: &IdMap<Rect>) {
|
||||||
if self.focus_direction.is_cardinal() {
|
if self.focus_direction.is_cardinal() {
|
||||||
if let Some(found_widget) = self.find_widget_in_direction(used_ids) {
|
if let Some(found_widget) = self.find_widget_in_direction(used_ids) {
|
||||||
self.id = Some(found_widget);
|
self.focused_widget = Some(FocusWidget::new(found_widget));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = self.id {
|
if let Some(focused_widget) = self.focused_widget {
|
||||||
// Allow calling `request_focus` one frame and not using it until next frame
|
// Allow calling `request_focus` one frame and not using it until next frame
|
||||||
let recently_gained_focus = self.id_previous_frame != Some(id);
|
let recently_gained_focus = self.id_previous_frame != Some(focused_widget.id);
|
||||||
|
|
||||||
if !recently_gained_focus && !used_ids.contains_key(&id) {
|
if !recently_gained_focus && !used_ids.contains_key(&focused_widget.id) {
|
||||||
// Dead-mans-switch: the widget with focus has disappeared!
|
// Dead-mans-switch: the widget with focus has disappeared!
|
||||||
self.id = None;
|
self.focused_widget = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -369,7 +381,7 @@ impl Focus {
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
{
|
{
|
||||||
if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
|
if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
|
||||||
self.id = Some(id);
|
self.focused_widget = Some(FocusWidget::new(id));
|
||||||
self.id_requested_by_accesskit = None;
|
self.id_requested_by_accesskit = None;
|
||||||
self.give_to_next = false;
|
self.give_to_next = false;
|
||||||
self.reset_focus();
|
self.reset_focus();
|
||||||
|
|
@ -382,23 +394,23 @@ impl Focus {
|
||||||
.or_insert(Rect::EVERYTHING);
|
.or_insert(Rect::EVERYTHING);
|
||||||
|
|
||||||
if self.give_to_next && !self.had_focus_last_frame(id) {
|
if self.give_to_next && !self.had_focus_last_frame(id) {
|
||||||
self.id = Some(id);
|
self.focused_widget = Some(FocusWidget::new(id));
|
||||||
self.give_to_next = false;
|
self.give_to_next = false;
|
||||||
} else if self.id == Some(id) {
|
} else if self.focused() == Some(id) {
|
||||||
if self.focus_direction == FocusDirection::Next && !self.is_focus_locked {
|
if self.focus_direction == FocusDirection::Next {
|
||||||
self.id = None;
|
self.focused_widget = None;
|
||||||
self.give_to_next = true;
|
self.give_to_next = true;
|
||||||
self.reset_focus();
|
self.reset_focus();
|
||||||
} else if self.focus_direction == FocusDirection::Previous && !self.is_focus_locked {
|
} else if self.focus_direction == FocusDirection::Previous {
|
||||||
self.id_next_frame = self.last_interested; // frame-delay so gained_focus works
|
self.id_next_frame = self.last_interested; // frame-delay so gained_focus works
|
||||||
self.reset_focus();
|
self.reset_focus();
|
||||||
}
|
}
|
||||||
} else if self.focus_direction == FocusDirection::Next
|
} else if self.focus_direction == FocusDirection::Next
|
||||||
&& self.id.is_none()
|
&& self.focused_widget.is_none()
|
||||||
&& !self.give_to_next
|
&& !self.give_to_next
|
||||||
{
|
{
|
||||||
// nothing has focus and the user pressed tab - give focus to the first widgets that wants it:
|
// nothing has focus and the user pressed tab - give focus to the first widgets that wants it:
|
||||||
self.id = Some(id);
|
self.focused_widget = Some(FocusWidget::new(id));
|
||||||
self.reset_focus();
|
self.reset_focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -424,7 +436,7 @@ impl Focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(focus_id) = self.id else {
|
let Some(current_focused) = self.focused_widget else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -449,7 +461,7 @@ impl Focus {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let Some(current_rect) = self.focus_widgets_cache.get(&focus_id) else {
|
let Some(current_rect) = self.focus_widgets_cache.get(¤t_focused.id) else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -457,7 +469,7 @@ impl Focus {
|
||||||
let mut best_id = None;
|
let mut best_id = None;
|
||||||
|
|
||||||
for (candidate_id, candidate_rect) in &self.focus_widgets_cache {
|
for (candidate_id, candidate_rect) in &self.focus_widgets_cache {
|
||||||
if Some(*candidate_id) == self.id {
|
if *candidate_id == current_focused.id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,46 +554,58 @@ impl Memory {
|
||||||
/// from the window and back.
|
/// from the window and back.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn has_focus(&self, id: Id) -> bool {
|
pub fn has_focus(&self, id: Id) -> bool {
|
||||||
self.interaction.focus.id == Some(id)
|
self.interaction.focus.focused() == Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which widget has keyboard focus?
|
/// Which widget has keyboard focus?
|
||||||
pub fn focus(&self) -> Option<Id> {
|
pub fn focus(&self) -> Option<Id> {
|
||||||
self.interaction.focus.id
|
self.interaction.focus.focused()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prevent keyboard focus from moving away from this widget even if users presses the tab key.
|
/// Set an event filter for a widget.
|
||||||
|
///
|
||||||
|
/// This allows you to control whether the widget will loose focus
|
||||||
|
/// when the user presses tab, arrow keys, or escape.
|
||||||
|
///
|
||||||
/// You must first give focus to the widget before calling this.
|
/// You must first give focus to the widget before calling this.
|
||||||
pub fn lock_focus(&mut self, id: Id, lock_focus: bool) {
|
pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) {
|
||||||
if self.had_focus_last_frame(id) && self.has_focus(id) {
|
if self.had_focus_last_frame(id) && self.has_focus(id) {
|
||||||
self.interaction.focus.is_focus_locked = lock_focus;
|
if let Some(focused) = &mut self.interaction.focus.focused_widget {
|
||||||
|
if focused.id == id {
|
||||||
|
focused.filter = event_filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the keyboard focus locked on this widget? If so the focus won't move even if the user presses the tab key.
|
/// Set an event filter for a widget.
|
||||||
pub fn has_lock_focus(&self, id: Id) -> bool {
|
///
|
||||||
if self.had_focus_last_frame(id) && self.has_focus(id) {
|
/// You must first give focus to the widget before calling this.
|
||||||
self.interaction.focus.is_focus_locked
|
#[deprecated = "Use set_focus_lock_filter instead"]
|
||||||
} else {
|
pub fn lock_focus(&mut self, id: Id, lock_focus: bool) {
|
||||||
false
|
self.set_focus_lock_filter(
|
||||||
}
|
id,
|
||||||
|
EventFilter {
|
||||||
|
tab: lock_focus,
|
||||||
|
arrows: lock_focus,
|
||||||
|
escape: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Give keyboard focus to a specific widget.
|
/// Give keyboard focus to a specific widget.
|
||||||
/// See also [`crate::Response::request_focus`].
|
/// See also [`crate::Response::request_focus`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn request_focus(&mut self, id: Id) {
|
pub fn request_focus(&mut self, id: Id) {
|
||||||
self.interaction.focus.id = Some(id);
|
self.interaction.focus.focused_widget = Some(FocusWidget::new(id));
|
||||||
self.interaction.focus.is_focus_locked = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Surrender keyboard focus for a specific widget.
|
/// Surrender keyboard focus for a specific widget.
|
||||||
/// See also [`crate::Response::surrender_focus`].
|
/// See also [`crate::Response::surrender_focus`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn surrender_focus(&mut self, id: Id) {
|
pub fn surrender_focus(&mut self, id: Id) {
|
||||||
if self.interaction.focus.id == Some(id) {
|
if self.interaction.focus.focused() == Some(id) {
|
||||||
self.interaction.focus.id = None;
|
self.interaction.focus.focused_widget = None;
|
||||||
self.interaction.focus.is_focus_locked = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -600,7 +624,7 @@ impl Memory {
|
||||||
/// Stop editing of active [`TextEdit`](crate::TextEdit) (if any).
|
/// Stop editing of active [`TextEdit`](crate::TextEdit) (if any).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn stop_text_input(&mut self) {
|
pub fn stop_text_input(&mut self) {
|
||||||
self.interaction.focus.id = None;
|
self.interaction.focus.focused_widget = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
|
||||||
|
|
@ -570,6 +570,16 @@ impl<'a> Slider<'a> {
|
||||||
let mut increment = 0usize;
|
let mut increment = 0usize;
|
||||||
|
|
||||||
if response.has_focus() {
|
if response.has_focus() {
|
||||||
|
ui.ctx().memory_mut(|m| {
|
||||||
|
m.set_focus_lock_filter(
|
||||||
|
response.id,
|
||||||
|
EventFilter {
|
||||||
|
arrows: true, // pressing arrows should not move focus to next widget
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
let (dec_key, inc_key) = match self.orientation {
|
let (dec_key, inc_key) = match self.orientation {
|
||||||
SliderOrientation::Horizontal => (Key::ArrowLeft, Key::ArrowRight),
|
SliderOrientation::Horizontal => (Key::ArrowLeft, Key::ArrowRight),
|
||||||
// Note that this is for moving the slider position,
|
// Note that this is for moving the slider position,
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ pub struct TextEdit<'t> {
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
desired_width: Option<f32>,
|
desired_width: Option<f32>,
|
||||||
desired_height_rows: usize,
|
desired_height_rows: usize,
|
||||||
lock_focus: bool,
|
event_filter: EventFilter,
|
||||||
cursor_at_end: bool,
|
cursor_at_end: bool,
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
align: Align2,
|
align: Align2,
|
||||||
|
|
@ -115,7 +115,11 @@ impl<'t> TextEdit<'t> {
|
||||||
interactive: true,
|
interactive: true,
|
||||||
desired_width: None,
|
desired_width: None,
|
||||||
desired_height_rows: 4,
|
desired_height_rows: 4,
|
||||||
lock_focus: false,
|
event_filter: EventFilter {
|
||||||
|
arrows: true, // moving the cursor is really important
|
||||||
|
tab: false, // tab is used to change focus, not to insert a tab character
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
cursor_at_end: true,
|
cursor_at_end: true,
|
||||||
min_size: Vec2::ZERO,
|
min_size: Vec2::ZERO,
|
||||||
align: Align2::LEFT_TOP,
|
align: Align2::LEFT_TOP,
|
||||||
|
|
@ -127,7 +131,7 @@ impl<'t> TextEdit<'t> {
|
||||||
/// Build a [`TextEdit`] focused on code editing.
|
/// Build a [`TextEdit`] focused on code editing.
|
||||||
/// By default it comes with:
|
/// By default it comes with:
|
||||||
/// - monospaced font
|
/// - monospaced font
|
||||||
/// - focus lock
|
/// - focus lock (tab will insert a tab character instead of moving focus)
|
||||||
pub fn code_editor(self) -> Self {
|
pub fn code_editor(self) -> Self {
|
||||||
self.font(TextStyle::Monospace).lock_focus(true)
|
self.font(TextStyle::Monospace).lock_focus(true)
|
||||||
}
|
}
|
||||||
|
|
@ -266,8 +270,8 @@ impl<'t> TextEdit<'t> {
|
||||||
///
|
///
|
||||||
/// When `true`, the widget will keep the focus and pressing TAB
|
/// When `true`, the widget will keep the focus and pressing TAB
|
||||||
/// will insert the `'\t'` character.
|
/// will insert the `'\t'` character.
|
||||||
pub fn lock_focus(mut self, b: bool) -> Self {
|
pub fn lock_focus(mut self, tab_will_indent: bool) -> Self {
|
||||||
self.lock_focus = b;
|
self.event_filter.tab = tab_will_indent;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,7 +356,9 @@ impl<'t> TextEdit<'t> {
|
||||||
let margin = self.margin;
|
let margin = self.margin;
|
||||||
let max_rect = ui.available_rect_before_wrap().shrink2(margin);
|
let max_rect = ui.available_rect_before_wrap().shrink2(margin);
|
||||||
let mut content_ui = ui.child_ui(max_rect, *ui.layout());
|
let mut content_ui = ui.child_ui(max_rect, *ui.layout());
|
||||||
|
|
||||||
let mut output = self.show_content(&mut content_ui);
|
let mut output = self.show_content(&mut content_ui);
|
||||||
|
|
||||||
let id = output.response.id;
|
let id = output.response.id;
|
||||||
let frame_rect = output.response.rect.expand2(margin);
|
let frame_rect = output.response.rect.expand2(margin);
|
||||||
ui.allocate_space(frame_rect.size());
|
ui.allocate_space(frame_rect.size());
|
||||||
|
|
@ -413,7 +419,7 @@ impl<'t> TextEdit<'t> {
|
||||||
interactive,
|
interactive,
|
||||||
desired_width,
|
desired_width,
|
||||||
desired_height_rows,
|
desired_height_rows,
|
||||||
lock_focus,
|
event_filter,
|
||||||
cursor_at_end,
|
cursor_at_end,
|
||||||
min_size,
|
min_size,
|
||||||
align,
|
align,
|
||||||
|
|
@ -569,7 +575,7 @@ impl<'t> TextEdit<'t> {
|
||||||
let mut cursor_range = None;
|
let mut cursor_range = None;
|
||||||
let prev_cursor_range = state.cursor_range(&galley);
|
let prev_cursor_range = state.cursor_range(&galley);
|
||||||
if interactive && ui.memory(|mem| mem.has_focus(id)) {
|
if interactive && ui.memory(|mem| mem.has_focus(id)) {
|
||||||
ui.memory_mut(|mem| mem.lock_focus(id, lock_focus));
|
ui.memory_mut(|mem| mem.set_focus_lock_filter(id, event_filter));
|
||||||
|
|
||||||
let default_cursor_range = if cursor_at_end {
|
let default_cursor_range = if cursor_at_end {
|
||||||
CursorRange::one(galley.end())
|
CursorRange::one(galley.end())
|
||||||
|
|
@ -589,6 +595,7 @@ impl<'t> TextEdit<'t> {
|
||||||
password,
|
password,
|
||||||
default_cursor_range,
|
default_cursor_range,
|
||||||
char_limit,
|
char_limit,
|
||||||
|
event_filter,
|
||||||
);
|
);
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
|
|
@ -880,6 +887,7 @@ fn events(
|
||||||
password: bool,
|
password: bool,
|
||||||
default_cursor_range: CursorRange,
|
default_cursor_range: CursorRange,
|
||||||
char_limit: usize,
|
char_limit: usize,
|
||||||
|
event_filter: EventFilter,
|
||||||
) -> (bool, CursorRange) {
|
) -> (bool, CursorRange) {
|
||||||
let mut cursor_range = state.cursor_range(galley).unwrap_or(default_cursor_range);
|
let mut cursor_range = state.cursor_range(galley).unwrap_or(default_cursor_range);
|
||||||
|
|
||||||
|
|
@ -898,7 +906,7 @@ fn events(
|
||||||
|
|
||||||
let mut any_change = false;
|
let mut any_change = false;
|
||||||
|
|
||||||
let events = ui.input(|i| i.events.clone()); // avoid dead-lock by cloning. TODO(emilk): optimize
|
let events = ui.input(|i| i.filtered_events(&event_filter));
|
||||||
for event in &events {
|
for event in &events {
|
||||||
let did_mutate_text = match event {
|
let did_mutate_text = match event {
|
||||||
Event::Copy => {
|
Event::Copy => {
|
||||||
|
|
@ -946,19 +954,15 @@ fn events(
|
||||||
pressed: true,
|
pressed: true,
|
||||||
modifiers,
|
modifiers,
|
||||||
..
|
..
|
||||||
} => {
|
} if multiline => {
|
||||||
if multiline && ui.memory(|mem| mem.has_lock_focus(id)) {
|
let mut ccursor = delete_selected(text, &cursor_range);
|
||||||
let mut ccursor = delete_selected(text, &cursor_range);
|
if modifiers.shift {
|
||||||
if modifiers.shift {
|
// TODO(emilk): support removing indentation over a selection?
|
||||||
// TODO(emilk): support removing indentation over a selection?
|
decrease_indentation(&mut ccursor, text);
|
||||||
decrease_indentation(&mut ccursor, text);
|
|
||||||
} else {
|
|
||||||
insert_text(&mut ccursor, text, "\t", char_limit);
|
|
||||||
}
|
|
||||||
Some(CCursorRange::one(ccursor))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
insert_text(&mut ccursor, text, "\t", char_limit);
|
||||||
}
|
}
|
||||||
|
Some(CCursorRange::one(ccursor))
|
||||||
}
|
}
|
||||||
Event::Key {
|
Event::Key {
|
||||||
key: Key::Enter,
|
key: Key::Enter,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue