Skip to content

Commit 8581bcf

Browse files
authored
editable_text pointer observers (#23475)
# Objective Add pointer support for editable text, click to place the cursor and drag to extend selections. ## Solution Two new observer systems in the editable_text module, `on_pointer_press` and `on_pointer_drag`. Very simple implementation, just calculates the pointer's local position and queues it with the appropriate `TextEdit` variant.
1 parent ae7af25 commit 8581bcf

2 files changed

Lines changed: 92 additions & 3 deletions

File tree

_release-content/release-notes/text_input.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Text input"
33
authors: ["@ickshonpe", "@Zeophlite", "@alice-i-cecile"]
4-
pull_requests: [23282, 23455]
4+
pull_requests: [23282, 23455, 23475]
55
---
66

77
Entering text into an application is a common task, even for games.
@@ -14,6 +14,7 @@ Our initial text entry supports:
1414
- Press keys on your keyboard, get text (wow!)
1515
- A cursor that can be moved forward and back in response to the left and right arrow keys
1616
- Selection rectangles (hold shift)
17+
- Pointer support, click to place the cursor and drag to extend selection
1718
- Backspace and Delete
1819
- Unicode-aware navigation and editing: 1 byte/char != 1 character
1920
- Bidirectional text support, allowing both left-to-right and right-to-left scripts

crates/bevy_ui_widgets/src/editable_text.rs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ use bevy_app::{App, Plugin};
1010
use bevy_ecs::prelude::*;
1111
use bevy_input::keyboard::{Key, KeyCode, KeyboardInput};
1212
use bevy_input::ButtonInput;
13-
use bevy_input_focus::FocusedInput;
13+
use bevy_input_focus::{FocusedInput, InputFocus};
14+
use bevy_picking::events::{Drag, Pointer, Press};
15+
use bevy_picking::pointer::PointerButton;
1416
use bevy_text::{EditableText, TextEdit};
1517
use bevy_ui::{widget::TextNodeFlags, ContentSize, Node};
18+
use bevy_ui::{ComputedNode, ComputedUiRenderTargetInfo, UiGlobalTransform, UiScale};
1619
use smol_str::SmolStr;
1720

1821
/// System that processes keyboard input events into text edit actions for focused [`EditableText`] widgets.
@@ -68,6 +71,89 @@ fn on_focused_keyboard_input(
6871
trigger.propagate(should_propagate);
6972
}
7073

74+
/// System that processes pointer press events into text edit actions for [`EditableText`] widgets.
75+
///
76+
/// Note that this does not immediately apply the edits; they are queued up in [`EditableText::pending_edits`],
77+
/// and then applied later by the [`apply_text_edits`](`bevy_text::apply_text_edits`) system.
78+
fn on_pointer_press(
79+
mut press: On<Pointer<Press>>,
80+
mut text_input_query: Query<(
81+
&mut EditableText,
82+
&ComputedNode,
83+
&ComputedUiRenderTargetInfo,
84+
&UiGlobalTransform,
85+
)>,
86+
keys: Res<ButtonInput<Key>>,
87+
mut input_focus: ResMut<InputFocus>,
88+
ui_scale: Res<UiScale>,
89+
) {
90+
if press.button != PointerButton::Primary {
91+
return;
92+
}
93+
94+
let Ok((mut editable_text, node, target, transform)) = text_input_query.get_mut(press.entity)
95+
else {
96+
return;
97+
};
98+
99+
let Some(local_pos) = transform.try_inverse().map(|inverse| {
100+
inverse
101+
.transform_point2(press.pointer_location.position * target.scale_factor() / ui_scale.0)
102+
+ 0.5 * node.size()
103+
}) else {
104+
return;
105+
};
106+
107+
editable_text
108+
.pending_edits
109+
.push(if keys.pressed(Key::Shift) {
110+
TextEdit::ShiftClickExtension
111+
} else {
112+
TextEdit::MoveToPoint
113+
}(local_pos));
114+
115+
input_focus.set(press.entity);
116+
117+
press.propagate(false);
118+
}
119+
120+
/// System that processes pointer drag events into text edit actions for [`EditableText`] widgets.
121+
///
122+
/// Note that this does not immediately apply the edits; they are queued up in [`EditableText::pending_edits`],
123+
/// and then applied later by the [`apply_text_edits`](`bevy_text::apply_text_edits`) system.
124+
fn on_pointer_drag(
125+
mut drag: On<Pointer<Drag>>,
126+
mut text_input_query: Query<(
127+
&mut EditableText,
128+
&ComputedNode,
129+
&ComputedUiRenderTargetInfo,
130+
&UiGlobalTransform,
131+
)>,
132+
ui_scale: Res<UiScale>,
133+
) {
134+
if drag.button != PointerButton::Primary {
135+
return;
136+
}
137+
138+
let Ok((mut editable_text, node, target, transform)) = text_input_query.get_mut(drag.entity)
139+
else {
140+
return;
141+
};
142+
143+
let Some(local_pos) = transform.try_inverse().map(|inverse| {
144+
inverse
145+
.transform_point2(drag.pointer_location.position * target.scale_factor() / ui_scale.0)
146+
+ 0.5 * node.size()
147+
}) else {
148+
return;
149+
};
150+
151+
editable_text
152+
.pending_edits
153+
.push(TextEdit::ExtendSelectionToPoint(local_pos));
154+
drag.propagate(false);
155+
}
156+
71157
/// Enables support for the [`EditableText`] widget.
72158
///
73159
/// Contains the systems and observers necessary to update widget state and handle user input.
@@ -81,7 +167,9 @@ pub struct EditableTextInputPlugin;
81167

82168
impl Plugin for EditableTextInputPlugin {
83169
fn build(&self, app: &mut App) {
84-
app.add_observer(on_focused_keyboard_input);
170+
app.add_observer(on_focused_keyboard_input)
171+
.add_observer(on_pointer_drag)
172+
.add_observer(on_pointer_press);
85173

86174
// These components cannot be registered in `bevy_text` where `EditableText` is defined,
87175
// because that would create a circular dependency between `bevy_text` and `bevy_ui`.

0 commit comments

Comments
 (0)