Skip to content

Commit f303c00

Browse files
committed
core: Implement handling of text control input
1 parent cf4c4f8 commit f303c00

File tree

6 files changed

+198
-20
lines changed

6 files changed

+198
-20
lines changed

core/src/display_object/edit_text.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::backend::input::MouseCursor;
55
use crate::context::{RenderContext, UpdateContext};
66
use crate::display_object::{DisplayObjectBase, TDisplayObject};
77
use crate::drawing::Drawing;
8-
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode};
8+
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, TextControlCode};
99
use crate::font::{round_down_to_pixel, Glyph};
1010
use crate::html::{BoxBounds, FormatSpans, LayoutBox, LayoutContent, TextFormat};
1111
use crate::prelude::*;
@@ -1056,6 +1056,87 @@ impl<'gc> EditText<'gc> {
10561056
None
10571057
}
10581058

1059+
pub fn text_control_input(
1060+
self,
1061+
control_code: TextControlCode,
1062+
context: &mut UpdateContext<'_, 'gc, '_>,
1063+
) {
1064+
if !self.0.read().is_editable && control_code.is_edit_input() {
1065+
return;
1066+
}
1067+
1068+
if let Some(selection) = self.selection() {
1069+
let mut changed = false;
1070+
let is_selectable = self.0.read().is_selectable;
1071+
match control_code {
1072+
TextControlCode::SelectAll => {
1073+
if is_selectable {
1074+
self.set_selection(
1075+
Some(TextSelection::for_range(0, self.text().len())),
1076+
context.gc_context,
1077+
);
1078+
}
1079+
}
1080+
TextControlCode::Copy => {
1081+
if !selection.is_caret() {
1082+
let text = &self.text()[selection.start()..selection.end()];
1083+
context.input.set_clipboard_content(text.to_string());
1084+
}
1085+
}
1086+
TextControlCode::Paste => {
1087+
let text = &context.input.clipboard_content();
1088+
self.replace_text(selection.start(), selection.end(), text, context);
1089+
let new_start = selection.start() + text.len();
1090+
if is_selectable {
1091+
self.set_selection(
1092+
Some(TextSelection::for_position(new_start)),
1093+
context.gc_context,
1094+
);
1095+
} else {
1096+
self.set_selection(
1097+
Some(TextSelection::for_position(self.text().len())),
1098+
context.gc_context,
1099+
);
1100+
}
1101+
changed = true;
1102+
}
1103+
TextControlCode::Cut => {
1104+
if !selection.is_caret() {
1105+
let text = &self.text()[selection.start()..selection.end()];
1106+
context.input.set_clipboard_content(text.to_string());
1107+
1108+
self.replace_text(selection.start(), selection.end(), "", context);
1109+
if is_selectable {
1110+
self.set_selection(
1111+
Some(TextSelection::for_position(selection.start())),
1112+
context.gc_context,
1113+
);
1114+
} else {
1115+
self.set_selection(
1116+
Some(TextSelection::for_position(self.text().len())),
1117+
context.gc_context,
1118+
);
1119+
}
1120+
changed = true;
1121+
}
1122+
}
1123+
_ => {}
1124+
}
1125+
if changed {
1126+
let globals = context.avm1.global_object_cell();
1127+
let swf_version = context.swf.header().version;
1128+
let mut activation = Activation::from_nothing(
1129+
context.reborrow(),
1130+
ActivationIdentifier::root("[Propagate Text Binding]"),
1131+
swf_version,
1132+
globals,
1133+
self.into(),
1134+
);
1135+
self.propagate_text_binding(&mut activation);
1136+
}
1137+
}
1138+
}
1139+
10591140
pub fn text_input(self, character: char, context: &mut UpdateContext<'_, 'gc, '_>) {
10601141
if !self.0.read().is_editable {
10611142
return;

core/src/events.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub enum PlayerEvent {
1111
MouseLeft,
1212
MouseWheel { delta: MouseWheelDelta },
1313
TextInput { codepoint: char },
14+
TextControl { code: TextControlCode },
1415
}
1516

1617
/// The distance scrolled by the mouse wheel.
@@ -137,6 +138,29 @@ impl ClipEvent {
137138
}
138139
}
139140

141+
/// Control inputs to a text field
142+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
143+
pub enum TextControlCode {
144+
// TODO: Extend this
145+
SelectAll,
146+
Copy,
147+
Paste,
148+
Cut,
149+
Backspace,
150+
Enter,
151+
Delete,
152+
}
153+
154+
impl TextControlCode {
155+
/// Indicates whether this is an event that edits the text content
156+
pub fn is_edit_input(self) -> bool {
157+
matches!(
158+
self,
159+
Self::Paste | Self::Cut | Self::Backspace | Self::Enter | Self::Delete
160+
)
161+
}
162+
}
163+
140164
/// Flash virtual keycode.
141165
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
142166
#[repr(u8)]

core/src/player.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,14 @@ impl Player {
689689
});
690690
}
691691

692+
if let PlayerEvent::TextControl { code } = event {
693+
self.mutate_with_update_context(|context| {
694+
if let Some(text) = context.focus_tracker.get().and_then(|o| o.as_edit_text()) {
695+
text.text_control_input(code, context);
696+
}
697+
});
698+
}
699+
692700
// Propagate clip events.
693701
self.mutate_with_update_context(|context| {
694702
let (clip_event, listener) = match event {

desktop/src/input.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clipboard::{ClipboardContext, ClipboardProvider};
22
use ruffle_core::backend::input::{InputBackend, MouseCursor};
3-
use ruffle_core::events::{KeyCode, PlayerEvent};
3+
use ruffle_core::events::{KeyCode, PlayerEvent, TextControlCode};
44
use std::collections::HashSet;
55
use std::rc::Rc;
66
use winit::event::{ElementState, ModifiersState, VirtualKeyCode, WindowEvent};
@@ -36,13 +36,19 @@ impl WinitInputBackend {
3636
ElementState::Pressed => {
3737
if let Some(key) = input.virtual_keycode {
3838
self.keys_down.insert(key);
39-
self.last_char =
40-
winit_key_to_char(key, input.modifiers.contains(ModifiersState::SHIFT));
41-
if let Some(key_code) = winit_to_ruffle_key_code(key) {
42-
self.last_key = key_code;
43-
return Some(PlayerEvent::KeyDown { key_code });
39+
if let Some(code) = winit_to_ruffle_text_control(key, input.modifiers) {
40+
return Some(PlayerEvent::TextControl { code });
4441
} else {
45-
self.last_key = KeyCode::Unknown;
42+
self.last_char = winit_key_to_char(
43+
key,
44+
input.modifiers.contains(ModifiersState::SHIFT),
45+
);
46+
if let Some(key_code) = winit_to_ruffle_key_code(key) {
47+
self.last_key = key_code;
48+
return Some(PlayerEvent::KeyDown { key_code });
49+
} else {
50+
self.last_key = KeyCode::Unknown;
51+
}
4652
}
4753
}
4854
}
@@ -460,3 +466,24 @@ fn winit_key_to_char(key_code: VirtualKeyCode, is_shift_down: bool) -> Option<ch
460466
};
461467
Some(out)
462468
}
469+
470+
/// Converts a `VirtualKeyCode` and `ModifiersState` to a Ruffle `TextControlCode`.
471+
/// Returns `None` if there is no match.
472+
fn winit_to_ruffle_text_control(
473+
key: VirtualKeyCode,
474+
modifiers: ModifiersState,
475+
) -> Option<TextControlCode> {
476+
let ctrl_cmd = modifiers.contains(ModifiersState::CTRL)
477+
|| (modifiers.contains(ModifiersState::LOGO) && cfg!(target_os = "macos"));
478+
if ctrl_cmd {
479+
match key {
480+
VirtualKeyCode::A => Some(TextControlCode::SelectAll),
481+
VirtualKeyCode::C => Some(TextControlCode::Copy),
482+
VirtualKeyCode::V => Some(TextControlCode::Paste),
483+
VirtualKeyCode::X => Some(TextControlCode::Cut),
484+
_ => None,
485+
}
486+
} else {
487+
None
488+
}
489+
}

web/src/input.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use ruffle_core::backend::input::{InputBackend, MouseCursor};
2-
use ruffle_core::events::KeyCode;
2+
use ruffle_core::events::{KeyCode, TextControlCode};
33
use ruffle_web_common::JsResult;
44
use std::collections::HashSet;
55
use web_sys::{HtmlCanvasElement, KeyboardEvent};
@@ -13,6 +13,7 @@ pub struct WebInputBackend {
1313
cursor: MouseCursor,
1414
last_key: KeyCode,
1515
last_char: Option<char>,
16+
last_text_control: Option<TextControlCode>,
1617
}
1718

1819
impl WebInputBackend {
@@ -24,23 +25,33 @@ impl WebInputBackend {
2425
cursor: MouseCursor::Arrow,
2526
last_key: KeyCode::Unknown,
2627
last_char: None,
28+
last_text_control: None,
2729
}
2830
}
2931

3032
/// Register a key press for a given code string.
3133
pub fn keydown(&mut self, event: &KeyboardEvent) {
3234
let code = event.code();
33-
self.last_key = web_to_ruffle_key_code(&code).unwrap_or(KeyCode::Unknown);
34-
self.keys_down.insert(code);
35-
self.last_char = web_key_to_codepoint(&event.key());
35+
self.keys_down.insert(code.clone());
36+
let is_ctrl_cmd = event.ctrl_key(); // TODO: Use meta key if on MacOS
37+
self.last_text_control = web_to_text_control(&event.key(), is_ctrl_cmd);
38+
if self.last_text_control.is_none() {
39+
self.last_key = web_to_ruffle_key_code(&code).unwrap_or(KeyCode::Unknown);
40+
self.last_char = web_key_to_codepoint(&event.key());
41+
}
3642
}
3743

3844
/// Register a key release for a given code string.
3945
pub fn keyup(&mut self, event: &KeyboardEvent) {
4046
let code = event.code();
41-
self.last_key = web_to_ruffle_key_code(&code).unwrap_or(KeyCode::Unknown);
4247
self.keys_down.remove(&code);
48+
self.last_key = web_to_ruffle_key_code(&code).unwrap_or(KeyCode::Unknown);
4349
self.last_char = web_key_to_codepoint(&event.key());
50+
self.last_text_control = None;
51+
}
52+
53+
pub fn last_text_control(&self) -> Option<TextControlCode> {
54+
self.last_text_control
4455
}
4556

4657
fn update_mouse_cursor(&self) {
@@ -329,3 +340,26 @@ pub fn web_key_to_codepoint(key: &str) -> Option<char> {
329340
}
330341
}
331342
}
343+
344+
pub fn web_to_text_control(key: &str, ctrl_key: bool) -> Option<TextControlCode> {
345+
let mut chars = key.chars();
346+
let (c1, c2) = (chars.next(), chars.next());
347+
if c2.is_none() {
348+
// Single character.
349+
if ctrl_key {
350+
match c1 {
351+
// TODO: Extend this
352+
Some('a') => Some(TextControlCode::SelectAll),
353+
Some('c') => Some(TextControlCode::Copy),
354+
Some('v') => Some(TextControlCode::Paste),
355+
Some('x') => Some(TextControlCode::Cut),
356+
_ => None,
357+
}
358+
} else {
359+
None
360+
}
361+
} else {
362+
// TODO: Check for special characters.
363+
None
364+
}
365+
}

web/src/lib.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -730,13 +730,17 @@ impl Ruffle {
730730

731731
let key_code = input.last_key_code();
732732
let key_char = input.last_key_char();
733-
734-
if key_code != KeyCode::Unknown {
735-
core.handle_event(PlayerEvent::KeyDown { key_code });
736-
}
737-
738-
if let Some(codepoint) = key_char {
739-
core.handle_event(PlayerEvent::TextInput { codepoint });
733+
let text_control = input.last_text_control();
734+
735+
if let Some(code) = text_control {
736+
core.handle_event(PlayerEvent::TextControl { code });
737+
} else {
738+
if key_code != KeyCode::Unknown {
739+
core.handle_event(PlayerEvent::KeyDown { key_code });
740+
}
741+
if let Some(codepoint) = key_char {
742+
core.handle_event(PlayerEvent::TextInput { codepoint });
743+
}
740744
}
741745

742746
js_event.prevent_default();

0 commit comments

Comments
 (0)