core: Clean up clip event handlers

Remove ButtonEvent and subsume it in ClipEvent. Player now tosses
ClipEvents down the display tree, cleaning up some of the event code.
This commit is contained in:
Mike Welsh 2020-05-23 16:51:40 -07:00
parent 6929cea996
commit c427d55dbb
5 changed files with 178 additions and 215 deletions

View File

@ -20,7 +20,7 @@ mod morph_shape;
mod movie_clip;
mod text;
use crate::events::{ButtonEvent, ButtonEventResult, ClipEvent};
use crate::events::{ClipEvent, ClipEventResult};
pub use bitmap::Bitmap;
pub use button::Button;
pub use edit_text::EditText;
@ -720,26 +720,13 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into<DisplayObject<'gc>>
/// Executes and propagates the given clip event.
/// Events execute inside-out; the deepest child will react first, followed by its parent, and
/// so forth.
fn propagate_button_event(
fn handle_clip_event(
&self,
context: &mut UpdateContext<'_, 'gc, '_>,
event: ButtonEvent,
) -> ButtonEventResult {
for child in self.children() {
if child.propagate_button_event(context, event) == ButtonEventResult::Handled {
return ButtonEventResult::Handled;
}
}
ButtonEventResult::NotHandled
}
/// Executes and propagates the given clip event.
/// Events execute inside-out; the deepest child will react first, followed by its parent, and
/// so forth.
fn propagate_clip_event(&self, context: &mut UpdateContext<'_, 'gc, '_>, event: ClipEvent) {
for child in self.children() {
child.propagate_clip_event(context, event);
}
_avm: &mut Avm1<'gc>,
_context: &mut UpdateContext<'_, 'gc, '_>,
_event: ClipEvent,
) -> ClipEventResult {
ClipEventResult::NotHandled
}
fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>) {}

View File

@ -1,7 +1,7 @@
use crate::avm1::{Avm1, Object, StageObject, Value};
use crate::context::{ActionType, RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, TDisplayObject};
use crate::events::{ButtonEvent, ButtonEventResult, ButtonKeyCode};
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult};
use crate::prelude::*;
use crate::tag_utils::{SwfMovie, SwfSlice};
use gc_arena::{Collect, GcCell, MutationContext};
@ -77,17 +77,6 @@ impl<'gc> Button<'gc> {
))
}
pub fn handle_button_event(
&mut self,
avm: &mut Avm1<'gc>,
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
event: ButtonEvent,
) {
self.0
.write(context.gc_context)
.handle_button_event((*self).into(), avm, context, event)
}
pub fn set_sounds(self, gc_context: MutationContext<'gc, '_>, sounds: swf::ButtonSounds) {
let button = self.0.write(gc_context);
let mut static_data = button.static_data.write(gc_context);
@ -189,26 +178,6 @@ impl<'gc> TDisplayObject<'gc> for Button<'gc> {
}
}
fn propagate_button_event(
&self,
context: &mut UpdateContext<'_, 'gc, '_>,
event: ButtonEvent,
) -> ButtonEventResult {
for child in self.children() {
if child.propagate_button_event(context, event) == ButtonEventResult::Handled {
return ButtonEventResult::Handled;
}
}
match event {
ButtonEvent::KeyPress { key_code } => self.0.write(context.gc_context).run_actions(
context,
swf::ButtonActionCondition::KeyPress,
Some(key_code),
),
_ => ButtonEventResult::NotHandled,
}
}
fn object(&self) -> Value<'gc> {
self.0
.read()
@ -224,6 +193,28 @@ impl<'gc> TDisplayObject<'gc> for Button<'gc> {
fn allow_as_mask(&self) -> bool {
!self.0.read().children.is_empty()
}
/// Executes and propagates the given clip event.
/// Events execute inside-out; the deepest child will react first, followed by its parent, and
/// so forth.
fn handle_clip_event(
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
event: ClipEvent,
) -> ClipEventResult {
if event.propagates() {
for child in self.children() {
if child.handle_clip_event(avm, context, event) == ClipEventResult::Handled {
return ClipEventResult::Handled;
}
}
}
self.0
.write(context.gc_context)
.handle_clip_event((*self).into(), avm, context, event)
}
}
impl<'gc> ButtonData<'gc> {
@ -307,57 +298,57 @@ impl<'gc> ButtonData<'gc> {
}
}
fn handle_button_event(
fn handle_clip_event(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
event: ButtonEvent,
) {
event: ClipEvent,
) -> ClipEventResult {
let mut handled = ClipEventResult::NotHandled;
// Translate the clip event to a button event, based on how the button state changes.
let cur_state = self.state;
let new_state = match event {
ButtonEvent::RollOut => ButtonState::Up,
ButtonEvent::RollOver => ButtonState::Over,
ButtonEvent::Press => ButtonState::Down,
ButtonEvent::Release => ButtonState::Over,
ButtonEvent::KeyPress { key_code } => {
self.run_actions(
ClipEvent::RollOut => ButtonState::Up,
ClipEvent::RollOver => ButtonState::Over,
ClipEvent::Press => ButtonState::Down,
ClipEvent::Release => ButtonState::Over,
ClipEvent::KeyPress { key_code } => {
handled = self.run_actions(
context,
swf::ButtonActionCondition::KeyPress,
Some(key_code),
);
cur_state
}
_ => return ClipEventResult::NotHandled,
};
let button_event_handler = match (cur_state, new_state) {
match (cur_state, new_state) {
(ButtonState::Up, ButtonState::Over) => {
self.run_actions(context, swf::ButtonActionCondition::IdleToOverUp, None);
self.play_sound(context, self.static_data.read().up_to_over_sound.as_ref());
Some("onRollOver")
}
(ButtonState::Over, ButtonState::Up) => {
self.run_actions(context, swf::ButtonActionCondition::OverUpToIdle, None);
self.play_sound(context, self.static_data.read().over_to_up_sound.as_ref());
Some("onRollOut")
}
(ButtonState::Over, ButtonState::Down) => {
self.run_actions(context, swf::ButtonActionCondition::OverUpToOverDown, None);
self.play_sound(context, self.static_data.read().over_to_down_sound.as_ref());
Some("onPress")
}
(ButtonState::Down, ButtonState::Over) => {
self.run_actions(context, swf::ButtonActionCondition::OverDownToOverUp, None);
self.play_sound(context, self.static_data.read().down_to_over_sound.as_ref());
Some("onRelease")
}
_ => None,
_ => (),
};
// Queue ActionScript-defined event handlers after the SWF defined ones.
// (e.g., clip.onRelease = foo).
if context.swf.version() >= 6 {
if let Some(name) = button_event_handler {
if let Some(name) = event.method_name() {
context.action_queue.queue_actions(
self_display_object,
ActionType::Method {
@ -371,6 +362,8 @@ impl<'gc> ButtonData<'gc> {
}
self.set_state(self_display_object, avm, context, new_state);
handled
}
fn play_sound(
@ -393,16 +386,20 @@ impl<'gc> ButtonData<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
condition: swf::ButtonActionCondition,
key_code: Option<ButtonKeyCode>,
) -> ButtonEventResult {
let mut handled = ButtonEventResult::NotHandled;
) -> ClipEventResult {
let mut handled = ClipEventResult::NotHandled;
if let Some(parent) = self.base.parent {
for action in &self.static_data.read().actions {
if action.condition == condition
&& (action.condition != swf::ButtonActionCondition::KeyPress
|| action.key_code == key_code)
{
// KeyPress events are consumed when a button handles them.
if action.condition == swf::ButtonActionCondition::KeyPress {
handled = ClipEventResult::Handled;
}
// Note that AVM1 buttons run actions relative to their parent, not themselves.
handled = ButtonEventResult::Handled;
context.action_queue.queue_actions(
parent,
ActionType::Normal {

View File

@ -8,7 +8,7 @@ use crate::display_object::{
Bitmap, Button, DisplayObjectBase, EditText, Graphic, MorphShapeStatic, TDisplayObject, Text,
};
use crate::drawing::Drawing;
use crate::events::{ButtonKeyCode, ClipEvent};
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult};
use crate::font::Font;
use crate::prelude::*;
use crate::shape_utils::DrawCommand;
@ -463,17 +463,9 @@ impl<'gc> MovieClip<'gc> {
actions: SmallVec<[ClipAction; 2]>,
) {
let mut mc = self.0.write(gc_context);
mc.has_button_clip_event = actions.iter().any(|a| {
a.events.iter().any(|e| match e {
ClipEvent::KeyPress { .. }
| ClipEvent::Press
| ClipEvent::Release
| ClipEvent::ReleaseOutside
| ClipEvent::RollOut
| ClipEvent::RollOver => true,
_ => false,
})
});
mc.has_button_clip_event = actions
.iter()
.any(|a| a.events.iter().copied().any(ClipEvent::is_button_event));
mc.set_clip_actions(actions);
}
@ -601,14 +593,14 @@ impl<'gc> MovieClip<'gc> {
mc.drawing.draw_command(command);
}
pub fn run_clip_action(
pub fn run_clip_event(
self,
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
event: ClipEvent,
) {
self.0
.write(context.gc_context)
.run_clip_action(self.into(), context, event);
.run_clip_event(self.into(), context, event);
}
}
@ -633,10 +625,10 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
let mut mc = self.0.write(context.gc_context);
let is_load_frame = !mc.initialized();
if is_load_frame {
mc.run_clip_action((*self).into(), context, ClipEvent::Load);
mc.run_clip_event((*self).into(), context, ClipEvent::Load);
mc.set_initialized(true);
} else {
mc.run_clip_action((*self).into(), context, ClipEvent::EnterFrame);
mc.run_clip_event((*self).into(), context, ClipEvent::EnterFrame);
}
// Run my SWF tags.
@ -645,7 +637,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
}
if is_load_frame {
mc.run_clip_postaction((*self).into(), context, ClipEvent::Load);
mc.run_clip_postevent((*self).into(), context, ClipEvent::Load);
}
}
@ -671,27 +663,24 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
self_node: DisplayObject<'gc>,
point: (Twips, Twips),
) -> Option<DisplayObject<'gc>> {
if self.visible() && self.world_bounds().contains(point) {
if self.0.read().has_button_clip_event {
return Some(self_node);
}
if let Ok(object) = self.object().as_object() {
const MOUSE_EVENT_HANDLERS: [&str; 5] = [
"onPress",
"onRelease",
"onReleaseOutside",
"onRollOut",
"onRollOver",
];
if MOUSE_EVENT_HANDLERS
.iter()
.any(|handler| object.has_property(avm, context, handler))
{
if self.visible() {
if self.world_bounds().contains(point) {
if self.0.read().has_button_clip_event {
return Some(self_node);
}
if let Ok(object) = self.object().as_object() {
if ClipEvent::BUTTON_EVENT_METHODS
.iter()
.any(|handler| object.has_property(avm, context, handler))
{
return Some(self_node);
}
}
}
// Maybe we could skip recursing down at all if !world_bounds.contains(point),
// but a child button can have an invisible hit area outside the parent's bounds.
for child in self.0.read().children.values().rev() {
let result = child.mouse_pick(avm, context, *child, point);
if result.is_some() {
@ -703,13 +692,21 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
None
}
fn propagate_clip_event(&self, context: &mut UpdateContext<'_, 'gc, '_>, event: ClipEvent) {
for child in self.children() {
child.propagate_clip_event(context, event);
fn handle_clip_event(
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
event: ClipEvent,
) -> ClipEventResult {
if event.propagates() {
for child in self.children() {
if child.handle_clip_event(avm, context, event) == ClipEventResult::Handled {
return ClipEventResult::Handled;
}
}
}
self.0
.read()
.run_clip_action((*self).into(), context, event);
self.0.read().run_clip_event((*self).into(), context, event)
}
fn as_movie_clip(&self) -> Option<MovieClip<'gc>> {
@ -809,7 +806,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
{
let mut mc = self.0.write(context.gc_context);
mc.stop_audio_stream(context);
mc.run_clip_action((*self).into(), context, ClipEvent::Unload);
mc.run_clip_event((*self).into(), context, ClipEvent::Unload);
}
self.set_removed(context.gc_context, true);
}
@ -1323,12 +1320,14 @@ impl<'gc> MovieClipData<'gc> {
}
/// Run all actions for the given clip event.
fn run_clip_action(
fn run_clip_event(
&self,
self_display_object: DisplayObject<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
event: ClipEvent,
) {
) -> ClipEventResult {
let mut handled = ClipEventResult::NotHandled;
// TODO: What's the behavior for loaded SWF files?
if context.swf.version() >= 5 {
for clip_action in self
@ -1336,6 +1335,10 @@ impl<'gc> MovieClipData<'gc> {
.iter()
.filter(|action| action.events.contains(&event))
{
// KeyPress events are consumed by a single instance.
if matches!(clip_action.event, ClipEvent::KeyPress { .. }) {
handled = ClipEventResult::Handled;
}
context.action_queue.queue_actions(
self_display_object,
ActionType::Normal {
@ -1348,28 +1351,7 @@ impl<'gc> MovieClipData<'gc> {
// Queue ActionScript-defined event handlers after the SWF defined ones.
// (e.g., clip.onEnterFrame = foo).
if context.swf.version() >= 6 {
let name = match event {
ClipEvent::Construct => None,
ClipEvent::Data => Some("onData"),
ClipEvent::DragOut => Some("onDragOut"),
ClipEvent::DragOver => Some("onDragOver"),
ClipEvent::EnterFrame => Some("onEnterFrame"),
ClipEvent::Initialize => None,
ClipEvent::KeyDown => Some("onKeyDown"),
ClipEvent::KeyPress { .. } => None,
ClipEvent::KeyUp => Some("onKeyUp"),
ClipEvent::Load => Some("onLoad"),
ClipEvent::MouseDown => Some("onMouseDown"),
ClipEvent::MouseMove => Some("onMouseMove"),
ClipEvent::MouseUp => Some("onMouseUp"),
ClipEvent::Press => Some("onPress"),
ClipEvent::RollOut => Some("onRollOut"),
ClipEvent::RollOver => Some("onRollOver"),
ClipEvent::Release => Some("onRelease"),
ClipEvent::ReleaseOutside => Some("onReleaseOutside"),
ClipEvent::Unload => Some("onUnload"),
};
if let Some(name) = name {
if let Some(name) = event.method_name() {
context.action_queue.queue_actions(
self_display_object,
ActionType::Method {
@ -1382,6 +1364,8 @@ impl<'gc> MovieClipData<'gc> {
}
}
}
handled
}
/// Run clip actions that trigger after the clip's own actions.
@ -1393,7 +1377,7 @@ impl<'gc> MovieClipData<'gc> {
/// TODO: If it turns out other `Load` events need to be delayed, perhaps
/// we should change which frame triggers a `Load` event, rather than
/// making sure our actions run after the clip's.
fn run_clip_postaction(
fn run_clip_postevent(
&self,
self_display_object: DisplayObject<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,

View File

@ -12,27 +12,9 @@ pub enum PlayerEvent {
TextInput { codepoint: char },
}
/// The events that an AVM1 button can fire.
///
/// In Flash, these are created using `on` code on the button instance:
/// ```ignore
/// on(release) {
/// trace("Button clicked");
/// }
/// ```
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[allow(dead_code)]
pub enum ButtonEvent {
Press,
Release,
RollOut,
RollOver,
KeyPress { key_code: ButtonKeyCode },
}
/// Whether this button event was handled by some child.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ButtonEventResult {
pub enum ClipEventResult {
NotHandled,
Handled,
}
@ -63,6 +45,57 @@ pub enum ClipEvent {
Unload,
}
impl ClipEvent {
/// Method names for button event handles.
pub const BUTTON_EVENT_METHODS: [&'static str; 7] = [
"onDragOver",
"onDragOut",
"onPress",
"onRelease",
"onReleaseOutside",
"onRollOut",
"onRollOver",
];
/// Indicates that the event should be propagated down to children.
pub fn propagates(self) -> bool {
matches!(
self,
Self::MouseUp | Self::MouseDown | Self::MouseMove | Self::KeyPress { .. } | Self::KeyDown | Self::KeyUp
)
}
/// Indicates whether this is an event type used by Buttons (i.e., on that can be used in an `on` handler in Flash).
pub fn is_button_event(self) -> bool {
matches!(self, Self::DragOut | Self::DragOver | Self::KeyPress { .. } | Self::Press | Self::RollOut | Self::RollOver | Self::Release | Self::ReleaseOutside)
}
/// Returns the method name of the event handler for this event.
pub fn method_name(self) -> Option<&'static str> {
match self {
ClipEvent::Construct => None,
ClipEvent::Data => Some("onData"),
ClipEvent::DragOut => Some("onDragOut"),
ClipEvent::DragOver => Some("onDragOver"),
ClipEvent::EnterFrame => Some("onEnterFrame"),
ClipEvent::Initialize => None,
ClipEvent::KeyDown => Some("onKeyDown"),
ClipEvent::KeyPress { .. } => None,
ClipEvent::KeyUp => Some("onKeyUp"),
ClipEvent::Load => Some("onLoad"),
ClipEvent::MouseDown => Some("onMouseDown"),
ClipEvent::MouseMove => Some("onMouseMove"),
ClipEvent::MouseUp => Some("onMouseUp"),
ClipEvent::Press => Some("onPress"),
ClipEvent::RollOut => Some("onRollOut"),
ClipEvent::RollOver => Some("onRollOver"),
ClipEvent::Release => Some("onRelease"),
ClipEvent::ReleaseOutside => Some("onReleaseOutside"),
ClipEvent::Unload => Some("onUnload"),
}
}
}
/// Flash virtual keycode.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]

View File

@ -7,9 +7,7 @@ use crate::backend::{
};
use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext};
use crate::display_object::{MorphShape, MovieClip};
use crate::events::{
ButtonEvent, ButtonEventResult, ButtonKeyCode, ClipEvent, KeyCode, PlayerEvent,
};
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, PlayerEvent};
use crate::library::Library;
use crate::loader::LoadManager;
use crate::prelude::*;
@ -385,7 +383,7 @@ impl Player {
PlayerEvent::TextInput { codepoint }
if codepoint as u32 >= 32 && codepoint as u32 <= 126 =>
{
Some(ButtonEvent::KeyPress {
Some(ClipEvent::KeyPress {
key_code: ButtonKeyCode::try_from(codepoint as u8).unwrap(),
})
}
@ -393,7 +391,7 @@ impl Player {
// Special keys have custom values for keyPress.
PlayerEvent::KeyDown { key_code } => {
if let Some(key_code) = crate::events::key_code_to_button_key_code(key_code) {
Some(ButtonEvent::KeyPress { key_code })
Some(ClipEvent::KeyPress { key_code })
} else {
None
}
@ -402,12 +400,12 @@ impl Player {
};
if button_event.is_some() {
self.mutate_with_update_context(|_avm, context| {
self.mutate_with_update_context(|avm, context| {
let levels: Vec<DisplayObject<'_>> = context.levels.values().copied().collect();
for level in levels {
if let Some(button_event) = button_event {
let state = level.propagate_button_event(context, button_event);
if state == ButtonEventResult::Handled {
let state = level.handle_clip_event(avm, context, button_event);
if state == ClipEventResult::Handled {
return;
}
}
@ -426,12 +424,12 @@ impl Player {
};
if clip_event.is_some() || mouse_event_name.is_some() {
self.mutate_with_update_context(|_avm, context| {
self.mutate_with_update_context(|avm, context| {
let levels: Vec<DisplayObject<'_>> = context.levels.values().copied().collect();
for level in levels {
if let Some(clip_event) = clip_event {
level.propagate_clip_event(context, clip_event);
level.handle_clip_event(avm, context, clip_event);
}
}
@ -452,40 +450,20 @@ impl Player {
let mut is_mouse_down = self.is_mouse_down;
self.mutate_with_update_context(|avm, context| {
if let Some(node) = context.mouse_hovered_object {
if let Some(mut button) = node.clone().as_button() {
match event {
PlayerEvent::MouseDown { .. } => {
is_mouse_down = true;
needs_render = true;
button.handle_button_event(avm, context, ButtonEvent::Press);
}
PlayerEvent::MouseUp { .. } => {
is_mouse_down = false;
needs_render = true;
button.handle_button_event(avm, context, ButtonEvent::Release);
}
_ => (),
match event {
PlayerEvent::MouseDown { .. } => {
is_mouse_down = true;
needs_render = true;
node.handle_clip_event(avm, context, ClipEvent::Press);
}
}
if let Some(clip) = node.clone().as_movie_clip() {
match event {
PlayerEvent::MouseDown { .. } => {
is_mouse_down = true;
needs_render = true;
clip.run_clip_action(context, ClipEvent::Press);
}
PlayerEvent::MouseUp { .. } => {
is_mouse_down = false;
needs_render = true;
clip.run_clip_action(context, ClipEvent::Release);
}
_ => (),
PlayerEvent::MouseUp { .. } => {
is_mouse_down = false;
needs_render = true;
node.handle_clip_event(avm, context, ClipEvent::Release);
}
_ => (),
}
}
@ -550,30 +528,14 @@ impl Player {
if cur_hovered.map(|d| d.as_ptr()) != new_hovered.map(|d| d.as_ptr()) {
// RollOut of previous node.
if let Some(node) = cur_hovered {
match node {
DisplayObject::Button(mut button) => {
button.handle_button_event(avm, context, ButtonEvent::RollOut);
}
DisplayObject::MovieClip(clip) => {
clip.run_clip_action(context, ClipEvent::RollOut);
}
_ => (),
}
node.handle_clip_event(avm, context, ClipEvent::RollOut);
}
// RollOver on new node.
new_cursor = MouseCursor::Arrow;
if let Some(node) = new_hovered {
new_cursor = MouseCursor::Hand;
match node {
DisplayObject::Button(mut button) => {
button.handle_button_event(avm, context, ButtonEvent::RollOver);
}
DisplayObject::MovieClip(clip) => {
clip.run_clip_action(context, ClipEvent::RollOver);
}
_ => (),
}
node.handle_clip_event(avm, context, ClipEvent::RollOver);
}
context.mouse_hovered_object = new_hovered;