avm2: Implement `DisplayObjectContainer.getChildAt`.

This test doesn't work yet because it needs `avm2-domainscope` to get merged in.
This commit is contained in:
David Wendt 2020-10-09 21:35:09 -04:00 committed by Mike Welsh
parent f843399aff
commit d04f131cfe
8 changed files with 57 additions and 3 deletions

View File

@ -4,9 +4,11 @@ use crate::avm2::activation::Activation;
use crate::avm2::class::Class; use crate::avm2::class::Class;
use crate::avm2::method::Method; use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object; use crate::avm2::object::{Object, TObject};
use crate::avm2::traits::Trait;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use crate::display_object::TDisplayObject;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.DisplayObjectContainer`'s instance constructor. /// Implements `flash.display.DisplayObjectContainer`'s instance constructor.
@ -27,9 +29,34 @@ pub fn class_init<'gc>(
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// Implements `DisplayObjectContainer.getChildAt`
pub fn get_child_at<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(dobj) = this.and_then(|this| this.as_display_object()) {
let id = args
.get(0)
.cloned()
.unwrap_or(Value::Undefined)
.coerce_to_i32(activation)?;
let child = dobj.get_child_by_id(id).ok_or_else(|| {
format!(
"RangeError: Display object container has no child with id {}",
id
)
})?;
return Ok(child.object2());
}
Ok(Value::Undefined)
}
/// Construct `DisplayObjectContainer`'s class. /// Construct `DisplayObjectContainer`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new( let class = Class::new(
QName::new( QName::new(
Namespace::package("flash.display"), Namespace::package("flash.display"),
"DisplayObjectContainer", "DisplayObjectContainer",
@ -38,5 +65,14 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
Method::from_builtin(instance_init), Method::from_builtin(instance_init),
Method::from_builtin(class_init), Method::from_builtin(class_init),
mc, mc,
) );
let mut write = class.write(mc);
write.define_instance_trait(Trait::from_method(
QName::new(Namespace::public_namespace(), "getChildAt"),
Method::from_builtin(get_child_at),
));
class
} }

View File

@ -675,6 +675,11 @@ pub trait TDisplayObject<'gc>:
None None
} }
/// Get a child display object by it's depth.
fn get_child_by_id(&self, _id: Depth) -> Option<DisplayObject<'gc>> {
None
}
/// Get another level by level name. /// Get another level by level name.
/// ///
/// Since levels don't have instance names, this function instead parses /// Since levels don't have instance names, this function instead parses

View File

@ -440,6 +440,10 @@ impl<'gc> TDisplayObject<'gc> for Button<'gc> {
} }
self.set_removed(context.gc_context, true); self.set_removed(context.gc_context, true);
} }
fn get_child_by_id(&self, id: Depth) -> Option<DisplayObject<'gc>> {
self.0.read().children.get(&id).cloned()
}
} }
impl<'gc> ButtonData<'gc> { impl<'gc> ButtonData<'gc> {

View File

@ -1950,6 +1950,10 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
fn on_focus_changed(&self, context: MutationContext<'gc, '_>, focused: bool) { fn on_focus_changed(&self, context: MutationContext<'gc, '_>, focused: bool) {
self.0.write(context).has_focus = focused; self.0.write(context).has_focus = focused;
} }
fn get_child_by_id(&self, id: Depth) -> Option<DisplayObject<'gc>> {
self.0.read().children.get(&id).cloned()
}
} }
impl<'gc> MovieClipData<'gc> { impl<'gc> MovieClipData<'gc> {

View File

@ -411,6 +411,7 @@ swf_tests! {
(as3_movieclip_constr, "avm2/movieclip_constr", 1), (as3_movieclip_constr, "avm2/movieclip_constr", 1),
(as3_lazyinit, "avm2/lazyinit", 1), (as3_lazyinit, "avm2/lazyinit", 1),
(as3_trace, "avm2/trace", 1), (as3_trace, "avm2/trace", 1),
(as3_displayobjectcontainer_getchildat, "avm2/displayobjectcontainer_getchildat", 1),
} }
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.

View File

@ -0,0 +1,4 @@
//this.getChildAt(0)
[object ChildClip_1]
//this.getChildAt(0).child_method()
//Child method called