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

avm1: fix buttons that were not clickable after a goto #10862

Merged
merged 11 commits into from Jul 14, 2023
7 changes: 5 additions & 2 deletions core/src/display_object.rs
Expand Up @@ -1146,7 +1146,7 @@ pub trait TDisplayObject<'gc>:
self.base().avm1_removed()
}

// Sets whethe this object has been removed. Only applies to AVM1
// Sets whether this object has been removed. Only applies to AVM1
fn set_avm1_removed(&self, gc_context: MutationContext<'gc, '_>, value: bool) {
self.base_mut(gc_context).set_avm1_removed(value)
}
Expand Down Expand Up @@ -1319,7 +1319,10 @@ pub trait TDisplayObject<'gc>:
fn set_has_explicit_name(&self, gc_context: MutationContext<'gc, '_>, value: bool) {
self.base_mut(gc_context).set_has_explicit_name(value);
}

fn state(&self) -> Option<ButtonState> {
None
}
fn set_state(self, _context: &mut UpdateContext<'_, 'gc>, _state: ButtonState) {}
/// Run any start-of-frame actions for this display object.
///
/// When fired on `Stage`, this also emits the AVM2 `enterFrame` broadcast.
Expand Down
6 changes: 5 additions & 1 deletion core/src/display_object/avm1_button.rs
Expand Up @@ -123,7 +123,7 @@ impl<'gc> Avm1Button<'gc> {
///
/// This function instantiates children and thus must not be called whilst
/// the caller is holding a write lock on the button data.
fn set_state(
pub fn set_state(
mut self,
context: &mut crate::context::UpdateContext<'_, 'gc>,
state: ButtonState,
Expand Down Expand Up @@ -193,6 +193,10 @@ impl<'gc> Avm1Button<'gc> {
}
}

pub fn state(&self) -> Option<ButtonState> {
Some(self.0.read().state)
}

fn get_boolean_property(
self,
context: &mut UpdateContext<'_, 'gc>,
Expand Down
86 changes: 83 additions & 3 deletions core/src/player.rs
Expand Up @@ -1252,15 +1252,57 @@ impl Player {
));
}

let mut new_over_object_updated = false;
// Cancel hover if an object is removed from the stage.
if let Some(hovered) = context.mouse_over_object {
if !context.is_action_script_3() && hovered.as_displayobject().avm1_removed() {
context.mouse_over_object = None;
if let Some(new_object) = new_over_object {
if Self::check_display_object_equality(
new_object.as_displayobject(),
hovered.as_displayobject(),
) && Arc::ptr_eq(
&new_object.as_displayobject().movie(),
&hovered.as_displayobject().movie(),
) {
if let Some(state) = hovered.as_displayobject().state() {
new_object.as_displayobject().set_state(context, state);
}
context.mouse_over_object = Some(new_object);
new_over_object_updated = true;
}
}
}
}
if let Some(pressed) = context.mouse_down_object {
if !context.is_action_script_3() && pressed.as_displayobject().avm1_removed() {
context.mouse_down_object = None;
let mut display_object = None;

if let Some(root_clip) = context.stage.root_clip() {
display_object = Self::find_first_character_instance(
root_clip,
pressed.as_displayobject(),
);
}

if let Some(new_down_object) = display_object {
if new_down_object.depth() == pressed.as_displayobject().depth()
&& Arc::ptr_eq(
&new_down_object.movie(),
&pressed.as_displayobject().movie(),
)
{
if let Some(state) = pressed.as_displayobject().state() {
new_down_object.set_state(context, state);
}
context.mouse_down_object = if display_object.is_some() {
new_down_object.as_interactive()
} else {
None
};
}
}
}
}

Expand Down Expand Up @@ -1345,8 +1387,9 @@ impl Player {
}
}
}
context.mouse_over_object = new_over_object;

if !new_over_object_updated {
context.mouse_over_object = new_over_object;
}
// Handle presses and releases.
if is_mouse_button_changed {
if context.input.is_mouse_down() {
Expand All @@ -1364,10 +1407,18 @@ impl Player {
events.push((context.stage.into(), ClipEvent::MouseUpInside));
}

let released_inside = InteractiveObject::option_ptr_eq(
let mut released_inside = InteractiveObject::option_ptr_eq(
context.mouse_down_object,
context.mouse_over_object,
);
if let Some(down) = context.mouse_down_object {
if let Some(over) = context.mouse_over_object {
if !released_inside {
released_inside =
down.as_displayobject().id() == over.as_displayobject().id();
Flawake marked this conversation as resolved.
Show resolved Hide resolved
Flawake marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
if released_inside {
// Released inside the clicked object.
if let Some(down_object) = context.mouse_down_object {
Expand Down Expand Up @@ -1441,6 +1492,35 @@ impl Player {
needs_render
}

//Checks if two displayObjects have the same depth and id
fn check_display_object_equality(object1: DisplayObject, object2: DisplayObject) -> bool {
object1.depth() == object2.depth() && object1.id() == object2.id()
Flawake marked this conversation as resolved.
Show resolved Hide resolved
}
///This searches for a display object by it's id.
///When a button is being held down but the mouse stops hovering over the object
///we need to know if th button is still there after goto.
//TODO: is there a better place to place next two functions?
fn find_first_character_instance<'gc>(
obj: DisplayObject<'gc>,
previous_object: DisplayObject<'gc>,
) -> Option<DisplayObject<'gc>> {
if let Some(parent) = obj.as_container() {
for child in parent.iter_render_list() {
if Self::check_display_object_equality(child, previous_object)
&& Arc::ptr_eq(&child.movie(), &previous_object.movie())
{
return Some(child);
}

let display_object = Self::find_first_character_instance(child, previous_object);
if display_object.is_some() {
return display_object;
}
}
}
None
}

/// Preload all pending movies in the player, including the root movie.
///
/// This should be called periodically with a reasonable execution limit.
Expand Down
70 changes: 70 additions & 0 deletions tests/tests/swfs/avm1/button_goto/input.json
@@ -0,0 +1,70 @@
[
{
"type": "MouseMove",
"pos": [
160,
160
]
},
{
"type": "Wait"
},
{
"type": "Wait"
},
{
"type": "MouseDown",
"pos": [ 160.0, 160.0 ],
"btn": "Left"
},
{
"type": "Wait"
},
{
"type": "Wait"
},
{
"type": "MouseMove",
"pos": [
0,
0
]
},
{
"type": "Wait"
},
{
"type": "Wait"
},
{
"type": "MouseMove",
"pos": [
160,
160
]
},
{
"type": "Wait"
},
{
"type": "Wait"
},
{
"type": "MouseUp",
"pos": [ 160.0, 160.0 ],
"btn": "Left"
},
{
"type": "Wait"
},
{
"type": "Wait"
},
{
"type": "MouseMove",
"pos": [
0,
0
]
}
]
4 changes: 4 additions & 0 deletions tests/tests/swfs/avm1/button_goto/output.txt
@@ -0,0 +1,4 @@
rollOver
dragOut
dragOver
rollOut
Binary file added tests/tests/swfs/avm1/button_goto/test.fla
Binary file not shown.
Binary file added tests/tests/swfs/avm1/button_goto/test.swf
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm1/button_goto/test.toml
@@ -0,0 +1 @@
num_frames = 11