avm1: Fix some issues with `Selection.getFocus()` and `setFocus()`

This commit is contained in:
Toad06 2023-04-04 15:51:15 +02:00 committed by Mike Welsh
parent 7a6e6cf214
commit 5e165a0682
3 changed files with 62 additions and 66 deletions

View File

@ -2,7 +2,7 @@ use crate::avm1::activation::Activation;
use crate::avm1::error::Error; use crate::avm1::error::Error;
use crate::avm1::globals::as_broadcaster::BroadcasterFunctions; use crate::avm1::globals::as_broadcaster::BroadcasterFunctions;
use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::property_decl::{define_properties_on, Declaration};
use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::avm1::{Object, ScriptObject, Value};
use crate::display_object::{EditText, TDisplayObject, TextSelection}; use crate::display_object::{EditText, TDisplayObject, TextSelection};
use gc_arena::MutationContext; use gc_arena::MutationContext;
@ -108,10 +108,14 @@ pub fn get_focus<'gc>(
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let focus = activation.context.focus_tracker.get(); let focus = activation.context.focus_tracker.get();
match focus { Ok(match focus {
Some(focus) => Ok(focus.object()), Some(focus) => focus
None => Ok(Value::Null), .object()
} .coerce_to_string(activation)
.unwrap_or_default()
.into(),
None => Value::Null,
})
} }
pub fn set_focus<'gc>( pub fn set_focus<'gc>(
@ -121,37 +125,22 @@ pub fn set_focus<'gc>(
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let tracker = activation.context.focus_tracker; let tracker = activation.context.focus_tracker;
match args.get(0) { match args.get(0) {
None => Ok(false.into()),
Some(Value::Undefined | Value::Null) => { Some(Value::Undefined | Value::Null) => {
tracker.set(None, &mut activation.context); tracker.set(None, &mut activation.context);
Ok(true.into()) Ok(true.into())
} }
Some(Value::Object(obj)) => { Some(focus) => {
if let Some(display_object) = obj.as_display_object() { let start_clip = activation.target_clip_or_root();
let object = activation.resolve_target_display_object(start_clip, *focus, false)?;
if let Some(display_object) = object {
if display_object.is_focusable() { if display_object.is_focusable() {
tracker.set(Some(display_object), &mut activation.context); tracker.set(object, &mut activation.context);
return Ok(true.into());
} }
// [NA] Note: The documentation says true is success and false is failure,
// but from testing this seems to be opposite.
Ok(false.into())
} else {
Ok(true.into())
} }
Ok(false.into())
} }
Some(Value::MovieClip(_)) => {
let obj = args.get(0).unwrap().coerce_to_object(activation);
if let Some(display_object) = obj.as_display_object() {
if display_object.is_focusable() {
tracker.set(Some(display_object), &mut activation.context);
}
// [NA] Note: The documentation says true is success and false is failure,
// but from testing this seems to be opposite.
Ok(false.into())
} else {
Ok(true.into())
}
}
_ => Ok(false.into()),
} }
} }

View File

@ -1,7 +1,9 @@
use crate::avm1::Avm1; use crate::avm1::Avm1;
use crate::avm1::Value; use crate::avm1::Value;
use crate::context::UpdateContext; use crate::context::UpdateContext;
pub use crate::display_object::{DisplayObject, TDisplayObject, TDisplayObjectContainer}; pub use crate::display_object::{
DisplayObject, TDisplayObject, TDisplayObjectContainer, TextSelection,
};
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, GcCell, MutationContext};
#[derive(Clone, Copy, Collect)] #[derive(Clone, Copy, Collect)]
@ -22,44 +24,49 @@ impl<'gc> FocusTracker<'gc> {
focused_element: Option<DisplayObject<'gc>>, focused_element: Option<DisplayObject<'gc>>,
context: &mut UpdateContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) { ) {
if let Some(text_field) = focused_element.and_then(|e| e.as_edit_text()) {
if text_field.is_editable() {
context.ui.open_virtual_keyboard();
}
}
let old = std::mem::replace(&mut *self.0.write(context.gc_context), focused_element); let old = std::mem::replace(&mut *self.0.write(context.gc_context), focused_element);
if old.is_none() && focused_element.is_none() { if old.is_none() && focused_element.is_none() {
// We didn't have anything, we still don't, no change. // We didn't have anything, we still don't, no change.
return; return;
} }
if old.is_some() == focused_element.is_some() if !(old.is_some() == focused_element.is_some()
&& old.unwrap().as_ptr() == focused_element.unwrap().as_ptr() && old.unwrap().as_ptr() == focused_element.unwrap().as_ptr())
{ {
// We're setting it to the same object as before, no change. if let Some(old) = old {
return; old.on_focus_changed(context.gc_context, false);
}
if let Some(new) = focused_element {
new.on_focus_changed(context.gc_context, true);
}
tracing::info!("Focus is now on {:?}", focused_element);
if let Some(level0) = context.stage.root_clip() {
Avm1::notify_system_listeners(
level0,
context,
"Selection".into(),
"onSetFocus".into(),
&[
old.map(|v| v.object()).unwrap_or(Value::Null),
focused_element.map(|v| v.object()).unwrap_or(Value::Null),
],
);
}
} }
if let Some(old) = old { // This applies even if the focused element hasn't changed.
old.on_focus_changed(context.gc_context, false); if let Some(text_field) = focused_element.and_then(|e| e.as_edit_text()) {
} if text_field.is_editable() {
if let Some(new) = focused_element { let length = text_field.text_length();
new.on_focus_changed(context.gc_context, true); text_field.set_selection(
} Some(TextSelection::for_range(0, length)),
context.gc_context,
);
tracing::info!("Focus is now on {:?}", focused_element); context.ui.open_virtual_keyboard();
}
if let Some(level0) = context.stage.root_clip() {
Avm1::notify_system_listeners(
level0,
context,
"Selection".into(),
"onSetFocus".into(),
&[
old.map(|v| v.object()).unwrap_or(Value::Null),
focused_element.map(|v| v.object()).unwrap_or(Value::Null),
],
);
} }
} }
} }

View File

@ -26,7 +26,7 @@ _level0.a
false true
// Selection.setFocus(b) // Selection.setFocus(b)
! - onSetFocus ! - onSetFocus
@ -50,7 +50,7 @@ _level0.b
false true
// Selection.setSelection(0) // Selection.setSelection(0)
undefined undefined
@ -133,7 +133,7 @@ _level0.b
// Selection.setFocus(b) // Selection.setFocus(b)
false true
// b.focusEnabled = false; // b.focusEnabled = false;
// Selection.getBeginIndex() // Selection.getBeginIndex()
@ -195,7 +195,7 @@ _level0.c
false true
// Selection.setFocus(d) // Selection.setFocus(d)
false false
@ -246,7 +246,7 @@ _level0.button
false true
// Selection.setFocus(text_input // Selection.setFocus(text_input
! - onSetFocus ! - onSetFocus
@ -270,7 +270,7 @@ _level0.text_input
false true
// Selection.setFocus(text_selectable) // Selection.setFocus(text_selectable)
! - onSetFocus ! - onSetFocus
@ -294,7 +294,7 @@ _level0.text_selectable
false true
// Selection.setSelection(0) // Selection.setSelection(0)
undefined undefined
@ -414,7 +414,7 @@ _level0.text_non_selectable
false true
// Selection.setFocus(text_input) // Selection.setFocus(text_input)
! - onSetFocus ! - onSetFocus
@ -438,7 +438,7 @@ _level0.text_input
false true
// Selection.setSelection(1, 3) // Selection.setSelection(1, 3)
// text_input.text // text_input.text