Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

text: Dispatch events on pasting text #19665

Merged
merged 9 commits into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 28 additions & 46 deletions core/src/display_object/edit_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ impl EditTextData<'_> {
}

impl<'gc> EditText<'gc> {
const ANY_NEWLINE: [char; 2] = ['\n', '\r'];

// This seems to be OS-independent
const INPUT_NEWLINE: char = '\r';

Expand Down Expand Up @@ -1711,7 +1713,7 @@ impl<'gc> EditText<'gc> {
let is_selectable = self.is_selectable();
match control_code {
TextControlCode::Enter => {
self.text_input(Self::INPUT_NEWLINE, context);
self.text_input(Self::INPUT_NEWLINE.to_string(), context);
}
TextControlCode::MoveLeft
| TextControlCode::MoveLeftWord
Expand Down Expand Up @@ -1779,30 +1781,7 @@ impl<'gc> EditText<'gc> {
break 'paste;
}

let text = self.0.read().restrict.filter_allowed(&text);
let text = WString::from_utf8(&text);
let mut text = text.as_wstr();

if text.len() > self.available_chars() && self.available_chars() > 0 {
text = &text[0..self.available_chars()];
}

if text.len() <= self.available_chars() {
self.replace_text(selection.start(), selection.end(), text, context);
let new_pos = selection.start() + text.len();
if is_selectable {
self.set_selection(
Some(TextSelection::for_position(new_pos)),
context.gc(),
);
} else {
self.set_selection(
Some(TextSelection::for_position(self.text().len())),
context.gc(),
);
}
changed = true;
}
self.text_input(text, context);
}
TextControlCode::Cut => {
let text = &self.text()[selection.start()..selection.end()];
Expand Down Expand Up @@ -1979,28 +1958,31 @@ impl<'gc> EditText<'gc> {
self.text().get(pos).unwrap_or(0) == '\n' as u16
}

pub fn text_input(self, character: char, context: &mut UpdateContext<'gc>) {
if self.0.read().flags.contains(EditTextFlag::READ_ONLY)
|| (character.is_control() && character != Self::INPUT_NEWLINE)
|| self.available_chars() == 0
{
pub fn text_input(self, text: String, context: &mut UpdateContext<'gc>) {
if self.0.read().flags.contains(EditTextFlag::READ_ONLY) || self.available_chars() == 0 {
return;
}

if !self.is_multiline() && character == Self::INPUT_NEWLINE {
let text = if self.is_multiline() {
text
} else {
text.replace(&Self::ANY_NEWLINE[..], "")
};

if text.is_empty() {
return;
}

let text = WString::from_utf8(&text);

let Some(selection) = self.selection() else {
return;
};

let Some(character) = self.0.read().restrict.to_allowed(character) else {
return;
};
let filtered_text = self.0.read().restrict.filter_allowed(&text);

if let Avm2Value::Object(target) = self.object2() {
let character_string = AvmString::new_utf8(context.gc(), character.to_string());
let character_string = AvmString::new(context.gc(), text);

let mut activation = Avm2Activation::from_nothing(context);
let text_evt = Avm2EventObject::text_event(
Expand All @@ -2017,13 +1999,13 @@ impl<'gc> EditText<'gc> {
}
}

self.replace_text(
selection.start(),
selection.end(),
&WString::from_char(character),
context,
);
let new_pos = selection.start() + character.len_utf8();
let mut text = filtered_text.as_wstr();
if text.len() > self.available_chars() {
text = &text[0..self.available_chars()];
}

self.replace_text(selection.start(), selection.end(), text, context);
let new_pos = selection.start() + text.len();
self.set_selection(Some(TextSelection::for_position(new_pos)), context.gc());

let mut activation = Avm1Activation::from_nothing(
Expand Down Expand Up @@ -3477,11 +3459,11 @@ impl EditTextRestrict {
}
}

pub fn filter_allowed(&self, text: &str) -> String {
let mut filtered = String::with_capacity(text.len());
pub fn filter_allowed(&self, text: &WStr) -> WString {
let mut filtered = WString::with_capacity(text.len(), text.is_wide());
for c in text.chars() {
if let Some(c) = self.to_allowed(c) {
filtered.push(c);
if let Some(c) = c.ok().and_then(|c| self.to_allowed(c)) {
filtered.push_char(c);
}
}
filtered
Expand Down
2 changes: 1 addition & 1 deletion core/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,7 @@ impl Player {
if !key_press_handled {
if let Some(text) = context.focus_tracker.get_as_edit_text() {
if let InputEvent::TextInput { codepoint } = &event {
text.text_input(*codepoint, context);
text.text_input((*codepoint).to_string(), context);
}
if let InputEvent::TextControl { code } = &event {
text.text_control_input(*code, context);
Expand Down
40 changes: 40 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_events/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package {
import flash.display.*;
import flash.text.*;
import flash.events.*;

public class Test extends MovieClip {
public function Test() {
var tf = new TextField();
tf.type = "input";
tf.border = true;
tf.x = 10;
tf.y = 10;
tf.width = 200;
tf.height = 50;
addChild(tf);

tf.addEventListener("textInput", function(evt:TextEvent):void {
trace("input " + evt.text + ", " + tf.text);
});

tf.addEventListener("change", function(evt:Event):void {
trace("change " + tf.text);
});

stage.addEventListener("keyDown", function(evt:KeyboardEvent):void {
if (evt.keyCode >= 0 && evt.keyCode <= 256) {
trace("key down " + evt.keyCode + ", " + tf.text);
}
});

stage.addEventListener("keyUp", function(evt:KeyboardEvent):void {
if (evt.keyCode >= 0 && evt.keyCode <= 256) {
trace("key up " + evt.keyCode + ", " + tf.text);
}
});

stage.focus = tf;
}
}
}
10 changes: 10 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_events/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{ "type": "SetClipboardText", "text": "Test" },
{ "type": "KeyDown", "key": "Control" },
{ "type": "TextControl", "code": "Paste" },
{ "type": "KeyUp", "key": "Control" },
{ "type": "KeyDown", "key": { "Char": "x" } },
{ "type": "TextInput", "codepoint": "x" },
{ "type": "KeyUp", "key": { "Char": "x" } },
{ "type": "Wait" }
]
8 changes: 8 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_events/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
key down 17,
input Test,
change Test
key up 17, Test
key down 88, Test
input x, Test
change Testx
key up 88, Testx
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm2/edittext_paste_events/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_ticks = 1
43 changes: 43 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_maxchars/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package {
import flash.display.*;
import flash.text.*;
import flash.events.*;

public class Test extends MovieClip {
public function Test() {
var tf = new TextField();
tf.type = "input";
tf.border = true;
tf.x = 10;
tf.y = 10;
tf.width = 200;
tf.height = 50;
tf.maxChars = 5;
tf.text = "xxxxx";
addChild(tf);

tf.addEventListener("textInput", function(evt:TextEvent):void {
trace("input " + evt.text + ", " + tf.text);
});

tf.addEventListener("change", function(evt:Event):void {
trace("change " + tf.text);
});

stage.addEventListener("keyDown", function(evt:KeyboardEvent):void {
if (evt.keyCode >= 0 && evt.keyCode <= 256) {
trace("key down " + evt.keyCode + ", " + tf.text);
}
});

stage.addEventListener("keyUp", function(evt:KeyboardEvent):void {
if (evt.keyCode >= 0 && evt.keyCode <= 256) {
trace("key up " + evt.keyCode + ", " + tf.text);
}
});

stage.focus = tf;
tf.setSelection(3, 5);
}
}
}
7 changes: 7 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_maxchars/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{ "type": "SetClipboardText", "text": "a\nb" },
{ "type": "KeyDown", "key": "Control" },
{ "type": "TextControl", "code": "Paste" },
{ "type": "KeyUp", "key": "Control" },
{ "type": "Wait" }
]
4 changes: 4 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_maxchars/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
key down 17, xxxxx
input ab, xxxxx
change xxxab
key up 17, xxxab
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm2/edittext_paste_maxchars/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_ticks = 1
43 changes: 43 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_restrict/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package {
import flash.display.*;
import flash.text.*;
import flash.events.*;

public class Test extends MovieClip {
public function Test() {
var tf = new TextField();
tf.type = "input";
tf.border = true;
tf.x = 10;
tf.y = 10;
tf.width = 200;
tf.height = 50;
tf.restrict = "y";
tf.text = "xxxxx";
addChild(tf);

tf.addEventListener("textInput", function(evt:TextEvent):void {
trace("input " + evt.text + ", " + tf.text);
});

tf.addEventListener("change", function(evt:Event):void {
trace("change " + tf.text);
});

stage.addEventListener("keyDown", function(evt:KeyboardEvent):void {
if (evt.keyCode >= 0 && evt.keyCode <= 256) {
trace("key down " + evt.keyCode + ", " + tf.text);
}
});

stage.addEventListener("keyUp", function(evt:KeyboardEvent):void {
if (evt.keyCode >= 0 && evt.keyCode <= 256) {
trace("key up " + evt.keyCode + ", " + tf.text);
}
});

stage.focus = tf;
tf.setSelection(3, 5);
}
}
}
17 changes: 17 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_restrict/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[
{ "type": "SetClipboardText", "text": "abc" },
{ "type": "KeyDown", "key": "Control" },
{ "type": "TextControl", "code": "Paste" },
{ "type": "KeyUp", "key": "Control" },
{ "type": "SetClipboardText", "text": "ayb" },
{ "type": "KeyDown", "key": "Control" },
{ "type": "TextControl", "code": "Paste" },
{ "type": "KeyUp", "key": "Control" },
{ "type": "KeyDown", "key": { "Char": "x" } },
{ "type": "TextInput", "codepoint": "x" },
{ "type": "KeyUp", "key": { "Char": "x" } },
{ "type": "KeyDown", "key": { "Char": "y" } },
{ "type": "TextInput", "codepoint": "y" },
{ "type": "KeyUp", "key": { "Char": "y" } },
{ "type": "Wait" }
]
16 changes: 16 additions & 0 deletions tests/tests/swfs/avm2/edittext_paste_restrict/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
key down 17, xxxxx
input abc, xxxxx
change xxx
key up 17, xxx
key down 17, xxx
input ayb, xxx
change xxxy
key up 17, xxxy
key down 88, xxxy
input x, xxxy
change xxxy
key up 88, xxxy
key down 89, xxxy
input y, xxxy
change xxxyy
key up 89, xxxyy
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm2/edittext_paste_restrict/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_ticks = 1
40 changes: 40 additions & 0 deletions tests/tests/swfs/avm2/edittext_restrict_events/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package {
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.TextEvent;
import flash.ui.Keyboard;
import flash.text.TextField;

public class Test extends Sprite {
public function Test() {
var text = new TextField();
text.border = true;
text.width = 200;
text.height = 20;
text.type = "input";
text.restrict = "x";
text.maxChars = 5;
addChild(text);

stage.focus = text;
stage.addEventListener("keyDown", function(event:KeyboardEvent):void {
if (event.keyCode < 256) {
trace("key down: " + event.keyCode + ", " + text.text);
}
});
stage.addEventListener("keyUp", function(event:KeyboardEvent):void {
if (event.keyCode < 256) {
trace("key up: " + event.keyCode + ", " + text.text);
}
});
text.addEventListener("textInput", function(event:TextEvent):void {
trace("text input: " + event.text + ", " + text.text);
});
text.addEventListener("change", function(event:*):void {
trace("changed: " + text.text);
});
}
}
}
Loading
Loading