core: Correct the order of events that are fired when an `Avm2Button` is instantiated on the timeline.

This commit is contained in:
David Wendt 2021-04-27 22:31:16 -04:00 committed by Mike Welsh
parent 6a70c1b3e0
commit 11d7fe5107
2 changed files with 70 additions and 26 deletions

View File

@ -71,7 +71,7 @@ pub fn set_down_state<'gc>(
.ok() .ok()
.and_then(|val| val.as_display_object()); .and_then(|val| val.as_display_object());
btn.set_state_child(activation.context.gc_context, ButtonState::DOWN, new_state); btn.set_state_child(&mut activation.context, ButtonState::DOWN, new_state);
} }
Ok(Value::Undefined) Ok(Value::Undefined)
@ -114,7 +114,7 @@ pub fn set_over_state<'gc>(
.ok() .ok()
.and_then(|val| val.as_display_object()); .and_then(|val| val.as_display_object());
btn.set_state_child(activation.context.gc_context, ButtonState::OVER, new_state); btn.set_state_child(&mut activation.context, ButtonState::OVER, new_state);
} }
Ok(Value::Undefined) Ok(Value::Undefined)
@ -157,11 +157,7 @@ pub fn set_hit_test_state<'gc>(
.ok() .ok()
.and_then(|val| val.as_display_object()); .and_then(|val| val.as_display_object());
btn.set_state_child( btn.set_state_child(&mut activation.context, ButtonState::HIT_TEST, new_state);
activation.context.gc_context,
ButtonState::HIT_TEST,
new_state,
);
} }
Ok(Value::Undefined) Ok(Value::Undefined)
@ -204,7 +200,7 @@ pub fn set_up_state<'gc>(
.ok() .ok()
.and_then(|val| val.as_display_object()); .and_then(|val| val.as_display_object());
btn.set_state_child(activation.context.gc_context, ButtonState::UP, new_state); btn.set_state_child(&mut activation.context, ButtonState::UP, new_state);
} }
Ok(Value::Undefined) Ok(Value::Undefined)

View File

@ -157,11 +157,14 @@ impl<'gc> Avm2Button<'gc> {
/// button children. This means that, for example, a child that exists in /// button children. This means that, for example, a child that exists in
/// multiple states in the SWF will actually be instantiated multiple /// multiple states in the SWF will actually be instantiated multiple
/// times. /// times.
///
/// If the boolean parameter is `true`, then the caller of this function
/// should signal events on all children of the returned display object.
fn create_state( fn create_state(
self, self,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
swf_state: swf::ButtonState, swf_state: swf::ButtonState,
) -> DisplayObject<'gc> { ) -> (DisplayObject<'gc>, bool) {
let movie = self let movie = self
.movie() .movie()
.expect("All SWF-defined buttons should have movies"); .expect("All SWF-defined buttons should have movies");
@ -234,22 +237,17 @@ impl<'gc> Avm2Button<'gc> {
child.post_instantiation(context, child, None, Instantiator::Movie, false); child.post_instantiation(context, child, None, Instantiator::Movie, false);
child.construct_frame(context); child.construct_frame(context);
child (child, false)
} else { } else {
for (child, depth) in children { for (child, depth) in children {
let removed_child = state_sprite.replace_at_depth(context, child, depth.into()); state_sprite.replace_at_depth(context, child, depth.into());
child.set_parent(context.gc_context, Some(self.into())); child.set_parent(context.gc_context, Some(self.into()));
child.post_instantiation(context, child, None, Instantiator::Movie, false); child.post_instantiation(context, child, None, Instantiator::Movie, false);
child.construct_frame(context); child.construct_frame(context);
dispatch_added_event(self.into(), child, false, context);
if let Some(removed_child) = removed_child {
dispatch_removed_event(removed_child, context);
}
} }
state_sprite.into() (state_sprite.into(), true)
} }
} }
@ -272,17 +270,28 @@ impl<'gc> Avm2Button<'gc> {
/// Set the display object that represents a particular button state. /// Set the display object that represents a particular button state.
pub fn set_state_child( pub fn set_state_child(
self, self,
gc_context: MutationContext<'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
state: swf::ButtonState, state: swf::ButtonState,
child: Option<DisplayObject<'gc>>, child: Option<DisplayObject<'gc>>,
) { ) {
let child_was_on_stage = child.map(|c| c.is_on_stage(context)).unwrap_or(false);
let old_state_child = self.get_state_child(state);
match state { match state {
swf::ButtonState::UP => self.0.write(gc_context).up_state = child, swf::ButtonState::UP => self.0.write(context.gc_context).up_state = child,
swf::ButtonState::OVER => self.0.write(gc_context).over_state = child, swf::ButtonState::OVER => self.0.write(context.gc_context).over_state = child,
swf::ButtonState::DOWN => self.0.write(gc_context).down_state = child, swf::ButtonState::DOWN => self.0.write(context.gc_context).down_state = child,
swf::ButtonState::HIT_TEST => self.0.write(gc_context).hit_area = child, swf::ButtonState::HIT_TEST => self.0.write(context.gc_context).hit_area = child,
_ => (), _ => (),
} }
if let Some(new_state_child) = child {
dispatch_added_event(self.into(), new_state_child, child_was_on_stage, context);
}
if let Some(old_state_child) = old_state_child {
dispatch_removed_event(old_state_child, context);
}
} }
pub fn enabled(self) -> bool { pub fn enabled(self) -> bool {
@ -366,10 +375,11 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
); );
self.0.write(context.gc_context).object = Some(object.into()); self.0.write(context.gc_context).object = Some(object.into());
let up_state = self.create_state(context, swf::ButtonState::UP); let (up_state, up_should_fire) = self.create_state(context, swf::ButtonState::UP);
let over_state = self.create_state(context, swf::ButtonState::OVER); let (over_state, over_should_fire) = self.create_state(context, swf::ButtonState::OVER);
let down_state = self.create_state(context, swf::ButtonState::DOWN); let (down_state, down_should_fire) = self.create_state(context, swf::ButtonState::DOWN);
let hit_area = self.create_state(context, swf::ButtonState::HIT_TEST); let (hit_area, hit_should_fire) =
self.create_state(context, swf::ButtonState::HIT_TEST);
let mut write = self.0.write(context.gc_context); let mut write = self.0.write(context.gc_context);
write.up_state = Some(up_state); write.up_state = Some(up_state);
@ -380,6 +390,42 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
drop(write); drop(write);
if up_should_fire {
if let Some(up_container) = up_state.as_container() {
for (_depth, child) in up_container.iter_depth_list() {
dispatch_added_event((*self).into(), child, false, context);
}
}
}
if over_should_fire {
if let Some(over_container) = over_state.as_container() {
for (_depth, child) in over_container.iter_depth_list() {
dispatch_added_event((*self).into(), child, false, context);
}
}
}
if down_should_fire {
if let Some(down_container) = down_state.as_container() {
for (_depth, child) in down_container.iter_depth_list() {
dispatch_added_event((*self).into(), child, false, context);
}
}
}
if hit_should_fire {
if let Some(hit_container) = hit_area.as_container() {
for (_depth, child) in hit_container.iter_depth_list() {
dispatch_added_event((*self).into(), child, false, context);
}
}
}
self.frame_constructed(context);
//NOTE: Yes, we do have to run these in a different order from the
//regular run_frame method.
up_state.run_frame(context); up_state.run_frame(context);
over_state.run_frame(context); over_state.run_frame(context);
down_state.run_frame(context); down_state.run_frame(context);
@ -389,6 +435,8 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
over_state.run_frame_scripts(context); over_state.run_frame_scripts(context);
down_state.run_frame_scripts(context); down_state.run_frame_scripts(context);
hit_area.run_frame_scripts(context); hit_area.run_frame_scripts(context);
self.exit_frame(context);
} }
} }