avm2: Use inner mutability instead of `GcCell` in `Avm2Button`

This commit is contained in:
Moulins 2024-01-18 02:49:13 +01:00 committed by TÖRÖK Attila
parent d1cbe46e25
commit e014d7c023
3 changed files with 166 additions and 198 deletions

View File

@ -274,7 +274,7 @@ pub fn get_track_as_menu<'gc>(
/// Implements `trackAsMenu`'s setter
pub fn set_track_as_menu<'gc>(
activation: &mut Activation<'_, 'gc>,
_activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
@ -283,8 +283,8 @@ pub fn set_track_as_menu<'gc>(
.and_then(|this| this.as_avm2_button())
{
match args.get_bool(0) {
true => btn.set_button_tracking(&mut activation.context, ButtonTracking::Menu),
false => btn.set_button_tracking(&mut activation.context, ButtonTracking::Push),
true => btn.set_button_tracking(ButtonTracking::Menu),
false => btn.set_button_tracking(ButtonTracking::Push),
}
}
@ -341,7 +341,7 @@ pub fn get_use_hand_cursor<'gc>(
/// Implements `useHandCursor`'s setter
pub fn set_use_hand_cursor<'gc>(
activation: &mut Activation<'_, 'gc>,
_activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
@ -349,7 +349,7 @@ pub fn set_use_hand_cursor<'gc>(
.as_display_object()
.and_then(|this| this.as_avm2_button())
{
btn.set_use_hand_cursor(&mut activation.context, args.get_bool(0));
btn.set_use_hand_cursor(args.get_bool(0));
}
Ok(Value::Undefined)

View File

@ -17,9 +17,11 @@ use crate::prelude::*;
use crate::tag_utils::{SwfMovie, SwfSlice};
use crate::vminterface::Instantiator;
use core::fmt;
use gc_arena::{Collect, GcCell, Mutation};
use gc_arena::barrier::unlock;
use gc_arena::lock::{Lock, RefLock};
use gc_arena::{Collect, Gc, Mutation};
use ruffle_render::filters::Filter;
use std::cell::{Ref, RefMut};
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::sync::Arc;
use super::container::dispatch_added_to_stage_event;
@ -28,12 +30,12 @@ use super::interactive::Avm2MousePick;
#[derive(Clone, Collect, Copy)]
#[collect(no_drop)]
pub struct Avm2Button<'gc>(GcCell<'gc, Avm2ButtonData<'gc>>);
pub struct Avm2Button<'gc>(Gc<'gc, Avm2ButtonData<'gc>>);
impl fmt::Debug for Avm2Button<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Avm2Button")
.field("ptr", &self.0.as_ptr())
.field("ptr", &Gc::as_ptr(self.0))
.finish()
}
}
@ -41,59 +43,58 @@ impl fmt::Debug for Avm2Button<'_> {
#[derive(Clone, Collect)]
#[collect(no_drop)]
pub struct Avm2ButtonData<'gc> {
base: InteractiveObjectBase<'gc>,
base: RefLock<InteractiveObjectBase<'gc>>,
static_data: GcCell<'gc, ButtonStatic>,
static_data: Gc<'gc, ButtonStatic>,
/// The current button state to render.
state: ButtonState,
state: Cell<ButtonState>,
/// The display object tree to render when the button is in the UP state.
up_state: Option<DisplayObject<'gc>>,
up_state: Lock<Option<DisplayObject<'gc>>>,
/// The display object tree to render when the button is in the OVER state.
over_state: Option<DisplayObject<'gc>>,
over_state: Lock<Option<DisplayObject<'gc>>>,
/// The display object tree to render when the button is in the DOWN state.
down_state: Option<DisplayObject<'gc>>,
down_state: Lock<Option<DisplayObject<'gc>>>,
/// The display object tree to use for mouse hit checks.
hit_area: Option<DisplayObject<'gc>>,
hit_area: Lock<Option<DisplayObject<'gc>>>,
/// The current tracking mode of this button.
#[collect(require_static)]
tracking: ButtonTracking,
tracking: Cell<ButtonTracking>,
/// The class of this button.
///
/// If not specified in `SymbolClass`, this will be
/// `flash.display.SimpleButton`.
class: Avm2ClassObject<'gc>,
class: Lock<Avm2ClassObject<'gc>>,
/// The AVM2 representation of this button.
object: Option<Avm2Object<'gc>>,
object: Lock<Option<Avm2Object<'gc>>>,
has_focus: Cell<bool>,
enabled: Cell<bool>,
use_hand_cursor: Cell<bool>,
/// If this button needs to have it's child states constructed, or not.
///
/// All buttons start out unconstructed and have this flag set `true`.
/// This flag is consumed during frame construction.
needs_frame_construction: bool,
has_focus: bool,
enabled: bool,
use_hand_cursor: bool,
needs_frame_construction: Cell<bool>,
/// Skip the next `run_frame` call.
///
/// This flag exists due to a really odd feature of buttons: they run their
/// children for one frame before parents can run. Then they go back to the
/// normal AVM2 execution order for future frames.
skip_current_frame: bool,
skip_current_frame: Cell<bool>,
/// When we initially construct a button and run a nested frame,
/// we run the framescripts for our states in a different order then
/// we do for all other framescript runs.
weird_framescript_order: bool,
weird_framescript_order: Cell<bool>,
}
impl<'gc> Avm2Button<'gc> {
@ -103,39 +104,42 @@ impl<'gc> Avm2Button<'gc> {
context: &mut UpdateContext<'_, 'gc>,
construct_blank_states: bool,
) -> Self {
let static_data = ButtonStatic {
swf: source_movie.movie.clone(),
id: button.id,
records: button.records.clone(),
up_to_over_sound: None,
over_to_down_sound: None,
down_to_over_sound: None,
over_to_up_sound: None,
};
Avm2Button(GcCell::new(
context.gc_context,
Avm2Button(Gc::new(
context.gc(),
Avm2ButtonData {
base: Default::default(),
static_data: GcCell::new(context.gc_context, static_data),
state: self::ButtonState::Up,
hit_area: None,
up_state: None,
over_state: None,
down_state: None,
class: context.avm2.classes().simplebutton,
object: None,
needs_frame_construction: construct_blank_states,
tracking: if button.is_track_as_menu {
static_data: Gc::new(
context.gc(),
ButtonStatic {
swf: source_movie.movie.clone(),
id: button.id,
cell: RefCell::new(ButtonStaticMut {
records: button.records.clone(),
up_to_over_sound: None,
over_to_down_sound: None,
down_to_over_sound: None,
over_to_up_sound: None,
}),
},
),
state: Cell::new(self::ButtonState::Up),
hit_area: Lock::new(None),
up_state: Lock::new(None),
over_state: Lock::new(None),
down_state: Lock::new(None),
class: Lock::new(context.avm2.classes().simplebutton),
object: Lock::new(None),
needs_frame_construction: Cell::new(construct_blank_states),
tracking: Cell::new(if button.is_track_as_menu {
ButtonTracking::Menu
} else {
ButtonTracking::Push
},
has_focus: false,
enabled: true,
use_hand_cursor: true,
skip_current_frame: false,
weird_framescript_order: false,
}),
has_focus: Cell::new(false),
enabled: Cell::new(true),
use_hand_cursor: Cell::new(true),
skip_current_frame: Cell::new(false),
weird_framescript_order: Cell::new(false),
},
))
}
@ -152,9 +156,8 @@ impl<'gc> Avm2Button<'gc> {
Self::from_swf_tag(&button_record, &movie.into(), context, false)
}
pub fn set_sounds(self, gc_context: &Mutation<'gc>, sounds: swf::ButtonSounds) {
let button = self.0.write(gc_context);
let mut static_data = button.static_data.write(gc_context);
pub fn set_sounds(self, sounds: swf::ButtonSounds) {
let mut static_data = self.0.static_data.cell.borrow_mut();
static_data.up_to_over_sound = sounds.up_to_over_sound;
static_data.over_to_down_sound = sounds.over_to_down_sound;
static_data.down_to_over_sound = sounds.down_to_over_sound;
@ -163,9 +166,8 @@ impl<'gc> Avm2Button<'gc> {
/// Handles the ancient DefineButtonCxform SWF tag.
/// Set the color transform for all children of each state.
pub fn set_colors(self, gc_context: &Mutation<'gc>, color_transforms: &[swf::ColorTransform]) {
let button = self.0.write(gc_context);
let mut static_data = button.static_data.write(gc_context);
pub fn set_colors(self, color_transforms: &[swf::ColorTransform]) {
let mut static_data = self.0.static_data.cell.borrow_mut();
// This tag isn't documented well in SWF19. It is only used in very old SWF<=2 content.
// It applies color transforms to every character in a button, in sequence(?).
@ -196,9 +198,9 @@ impl<'gc> Avm2Button<'gc> {
let sprite_class = context.avm2.classes().sprite;
let mut children = Vec::new();
let static_data = self.0.read().static_data;
let static_data = self.0.static_data;
for record in static_data.read().records.iter() {
for record in static_data.cell.borrow().records.iter() {
if record.states.contains(swf_state) {
match context
.library
@ -206,14 +208,14 @@ impl<'gc> Avm2Button<'gc> {
.instantiate_by_id(record.id, context.gc_context)
{
Ok(child) => {
child.set_matrix(context.gc_context, record.matrix.into());
child.set_depth(context.gc_context, record.depth.into());
child.set_matrix(context.gc(), record.matrix.into());
child.set_depth(context.gc(), record.depth.into());
if swf_state != swf::ButtonState::HIT_TEST {
child.set_color_transform(context.gc_context, record.color_transform);
child.set_blend_mode(context.gc_context, record.blend_mode.into());
child.set_color_transform(context.gc(), record.color_transform);
child.set_blend_mode(context.gc(), record.blend_mode.into());
child.set_filters(
context.gc_context,
context.gc(),
record.filters.iter().map(Filter::from).collect(),
);
}
@ -223,7 +225,7 @@ impl<'gc> Avm2Button<'gc> {
Err(error) => {
tracing::error!(
"Button ID {}: could not instantiate child ID {}: {}",
static_data.read().id,
static_data.id,
record.id,
error
);
@ -232,7 +234,7 @@ impl<'gc> Avm2Button<'gc> {
}
}
self.invalidate_cached_bitmap(context.gc_context);
self.invalidate_cached_bitmap(context.gc());
// We manually call `construct_frame` for `child` and `state_sprite` - normally
// this would be done in the `DisplayObject` constructor, but SimpleButton does
@ -248,8 +250,8 @@ impl<'gc> Avm2Button<'gc> {
(child, false)
} else {
let state_sprite = MovieClip::new(movie, context.gc_context);
state_sprite.set_avm2_class(context.gc_context, Some(sprite_class));
let state_sprite = MovieClip::new(movie, context.gc());
state_sprite.set_avm2_class(context.gc(), Some(sprite_class));
state_sprite.set_parent(context, Some(self.into()));
catchup_display_object_to_frame(context, state_sprite.into());
@ -274,24 +276,23 @@ impl<'gc> Avm2Button<'gc> {
/// Get the rendered state of the button.
pub fn state(self) -> ButtonState {
self.0.read().state
self.0.state.get()
}
/// Change the rendered state of the button.
pub fn set_state(self, context: &mut UpdateContext<'_, 'gc>, state: ButtonState) {
self.invalidate_cached_bitmap(context.gc_context);
self.0.write(context.gc_context).state = state;
let button = self.0.read();
if let Some(state) = button.up_state {
self.invalidate_cached_bitmap(context.gc());
if let Some(state) = self.0.up_state.get() {
state.set_parent(context, None);
}
if let Some(state) = button.over_state {
if let Some(state) = self.0.over_state.get() {
state.set_parent(context, None);
}
if let Some(state) = button.down_state {
if let Some(state) = self.0.down_state.get() {
state.set_parent(context, None);
}
if let Some(state) = button.hit_area {
if let Some(state) = self.0.hit_area.get() {
state.set_parent(context, None);
}
if let Some(state) = self.get_state_child(state.into()) {
@ -302,10 +303,10 @@ impl<'gc> Avm2Button<'gc> {
/// Get the display object that represents a particular button state.
pub fn get_state_child(self, state: swf::ButtonState) -> Option<DisplayObject<'gc>> {
match state {
swf::ButtonState::UP => self.0.read().up_state,
swf::ButtonState::OVER => self.0.read().over_state,
swf::ButtonState::DOWN => self.0.read().down_state,
swf::ButtonState::HIT_TEST => self.0.read().hit_area,
swf::ButtonState::UP => self.0.up_state.get(),
swf::ButtonState::OVER => self.0.over_state.get(),
swf::ButtonState::DOWN => self.0.down_state.get(),
swf::ButtonState::HIT_TEST => self.0.hit_area.get(),
_ => None,
}
}
@ -319,13 +320,14 @@ impl<'gc> Avm2Button<'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);
let is_cur_state = swf::ButtonState::from(self.0.read().state) == state;
let is_cur_state = swf::ButtonState::from(self.0.state.get()) == state;
let write = Gc::write(context.gc(), self.0);
match state {
swf::ButtonState::UP => self.0.write(context.gc_context).up_state = child,
swf::ButtonState::OVER => self.0.write(context.gc_context).over_state = child,
swf::ButtonState::DOWN => self.0.write(context.gc_context).down_state = child,
swf::ButtonState::HIT_TEST => self.0.write(context.gc_context).hit_area = child,
swf::ButtonState::UP => unlock!(write, Avm2ButtonData, up_state).set(child),
swf::ButtonState::OVER => unlock!(write, Avm2ButtonData, over_state).set(child),
swf::ButtonState::DOWN => unlock!(write, Avm2ButtonData, down_state).set(child),
swf::ButtonState::HIT_TEST => unlock!(write, Avm2ButtonData, hit_area).set(child),
_ => (),
}
@ -366,64 +368,61 @@ impl<'gc> Avm2Button<'gc> {
}
pub fn enabled(self) -> bool {
self.0.read().enabled
self.0.enabled.get()
}
pub fn set_enabled(self, context: &mut UpdateContext<'_, 'gc>, enabled: bool) {
self.0.write(context.gc_context).enabled = enabled;
self.0.enabled.set(enabled);
if !enabled {
self.set_state(context, ButtonState::Up);
}
}
pub fn use_hand_cursor(self) -> bool {
self.0.read().use_hand_cursor
self.0.use_hand_cursor.get()
}
pub fn set_use_hand_cursor(self, context: &mut UpdateContext<'_, 'gc>, use_hand_cursor: bool) {
self.0.write(context.gc_context).use_hand_cursor = use_hand_cursor;
pub fn set_use_hand_cursor(self, use_hand_cursor: bool) {
self.0.use_hand_cursor.set(use_hand_cursor);
}
pub fn button_tracking(self) -> ButtonTracking {
self.0.read().tracking
self.0.tracking.get()
}
pub fn set_button_tracking(
self,
context: &mut UpdateContext<'_, 'gc>,
tracking: ButtonTracking,
) {
self.0.write(context.gc_context).tracking = tracking;
pub fn set_button_tracking(self, tracking: ButtonTracking) {
self.0.tracking.set(tracking);
}
pub fn set_avm2_class(self, mc: &Mutation<'gc>, class: Avm2ClassObject<'gc>) {
self.0.write(mc).class = class;
unlock!(Gc::write(mc, self.0), Avm2ButtonData, class).set(class);
}
}
impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
fn base(&self) -> Ref<DisplayObjectBase<'gc>> {
Ref::map(self.0.read(), |r| &r.base.base)
Ref::map(self.0.base.borrow(), |r| &r.base)
}
fn base_mut<'a>(&'a self, mc: &Mutation<'gc>) -> RefMut<'a, DisplayObjectBase<'gc>> {
RefMut::map(self.0.write(mc), |w| &mut w.base.base)
let data = unlock!(Gc::write(mc, self.0), Avm2ButtonData, base);
RefMut::map(data.borrow_mut(), |w| &mut w.base)
}
fn instantiate(&self, gc_context: &Mutation<'gc>) -> DisplayObject<'gc> {
Self(GcCell::new(gc_context, self.0.read().clone())).into()
fn instantiate(&self, mc: &Mutation<'gc>) -> DisplayObject<'gc> {
Self(Gc::new(mc, (*self.0).clone())).into()
}
fn as_ptr(&self) -> *const DisplayObjectPtr {
self.0.as_ptr() as *const DisplayObjectPtr
Gc::as_ptr(self.0) as *const DisplayObjectPtr
}
fn id(&self) -> CharacterId {
self.0.read().static_data.read().id
self.0.static_data.id
}
fn movie(&self) -> Arc<SwfMovie> {
self.0.read().static_data.read().swf.clone()
self.0.static_data.swf.clone()
}
fn post_instantiation(
@ -437,59 +436,49 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
}
fn enter_frame(&self, context: &mut UpdateContext<'_, 'gc>) {
let hit_area = self.0.read().hit_area;
if let Some(hit_area) = hit_area {
if let Some(hit_area) = self.0.hit_area.get() {
hit_area.enter_frame(context);
}
let up_state = self.0.read().up_state;
if let Some(up_state) = up_state {
if let Some(up_state) = self.0.up_state.get() {
up_state.enter_frame(context);
}
let down_state = self.0.read().down_state;
if let Some(down_state) = down_state {
if let Some(down_state) = self.0.down_state.get() {
down_state.enter_frame(context);
}
let over_state = self.0.read().over_state;
if let Some(over_state) = over_state {
if let Some(over_state) = self.0.over_state.get() {
over_state.enter_frame(context);
}
}
fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc>) {
let hit_area = self.0.read().hit_area;
if let Some(hit_area) = hit_area {
if let Some(hit_area) = self.0.hit_area.get() {
hit_area.construct_frame(context);
}
let up_state = self.0.read().up_state;
if let Some(up_state) = up_state {
if let Some(up_state) = self.0.up_state.get() {
up_state.construct_frame(context);
}
let down_state = self.0.read().down_state;
if let Some(down_state) = down_state {
if let Some(down_state) = self.0.down_state.get() {
down_state.construct_frame(context);
}
let over_state = self.0.read().over_state;
if let Some(over_state) = over_state {
if let Some(over_state) = self.0.over_state.get() {
over_state.construct_frame(context);
}
let needs_avm2_construction = self.0.read().object.is_none();
let class = self.0.read().class;
let needs_avm2_construction = self.0.object.get().is_none();
let class = self.0.class.get();
let needs_frame_construction = self.0.read().needs_frame_construction;
if needs_frame_construction {
if self.0.needs_frame_construction.get() {
if needs_avm2_construction {
let object_cell = unlock!(Gc::write(context.gc(), self.0), Avm2ButtonData, object);
let mut activation = Avm2Activation::from_nothing(context.reborrow());
match Avm2StageObject::for_display_object(&mut activation, (*self).into(), class) {
Ok(object) => {
self.0.write(activation.context.gc_context).object = Some(object.into())
}
Ok(object) => object_cell.set(Some(object.into())),
Err(e) => tracing::error!("Got {} when constructing AVM2 side of button", e),
};
if !self.placed_by_script() {
@ -500,7 +489,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
}
// Prevent re-entrantly constructing this button (since we may run a nested frame here)
self.0.write(context.gc_context).needs_frame_construction = false;
self.0.needs_frame_construction.set(false);
let (up_state, up_should_fire) = self.create_state(context, swf::ButtonState::UP);
let (over_state, over_should_fire) = self.create_state(context, swf::ButtonState::OVER);
let (down_state, down_should_fire) = self.create_state(context, swf::ButtonState::DOWN);
@ -520,14 +509,13 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
objs.iter().any(|display| display.as_movie_clip().is_some())
};
let mut write = self.0.write(context.gc_context);
write.up_state = Some(up_state);
write.over_state = Some(over_state);
write.down_state = Some(down_state);
write.hit_area = Some(hit_area);
write.skip_current_frame = true;
drop(write);
let write = Gc::write(context.gc(), self.0);
unlock!(write, Avm2ButtonData, up_state).set(Some(up_state));
unlock!(write, Avm2ButtonData, over_state).set(Some(over_state));
unlock!(write, Avm2ButtonData, down_state).set(Some(down_state));
unlock!(write, Avm2ButtonData, hit_area).set(Some(hit_area));
write.skip_current_frame.set(true);
write.needs_frame_construction.set(false);
let mut fire_state_events = |should_fire, state: DisplayObject<'gc>| {
if should_fire {
@ -565,7 +553,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
self.set_state(context, ButtonState::Up);
if has_movie_clip_state && self.movie().version() > 9 {
self.0.write(context.gc_context).weird_framescript_order = true;
self.0.weird_framescript_order.set(true);
let stage = context.stage;
stage.construct_frame(context);
@ -575,8 +563,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
stage.exit_frame(context);
}
let avm2_object = self.0.read().object;
if let Some(avm2_object) = avm2_object {
if let Some(avm2_object) = self.0.object.get() {
let mut activation = Avm2Activation::from_nothing(context.reborrow());
if let Err(e) = class.call_native_init(avm2_object.into(), &[], &mut activation)
{
@ -592,50 +579,37 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
}
fn run_frame_scripts(self, context: &mut UpdateContext<'_, 'gc>) {
if self.0.read().weird_framescript_order {
self.0.write(context.gc_context).weird_framescript_order = false;
let up_state = self.0.read().up_state;
if let Some(up_state) = up_state {
if self.0.weird_framescript_order.take() {
if let Some(up_state) = self.0.up_state.get() {
up_state.run_frame_scripts(context);
}
let over_state = self.0.read().over_state;
if let Some(over_state) = over_state {
if let Some(over_state) = self.0.over_state.get() {
over_state.run_frame_scripts(context);
}
let down_state = self.0.read().down_state;
if let Some(down_state) = down_state {
if let Some(down_state) = self.0.down_state.get() {
down_state.run_frame_scripts(context);
}
let hit_area = self.0.read().hit_area;
if let Some(hit_area) = hit_area {
if let Some(hit_area) = self.0.hit_area.get() {
hit_area.run_frame_scripts(context);
}
} else {
let hit_area = self.0.read().hit_area;
if let Some(hit_area) = hit_area {
if let Some(hit_area) = self.0.hit_area.get() {
hit_area.run_frame_scripts(context);
}
let up_state = self.0.read().up_state;
if let Some(up_state) = up_state {
if let Some(up_state) = self.0.up_state.get() {
up_state.run_frame_scripts(context);
}
let down_state = self.0.read().down_state;
if let Some(down_state) = down_state {
if let Some(down_state) = self.0.down_state.get() {
down_state.run_frame_scripts(context);
}
let over_state = self.0.read().over_state;
if let Some(over_state) = over_state {
if let Some(over_state) = self.0.over_state.get() {
over_state.run_frame_scripts(context);
}
}
}
fn render_self(&self, context: &mut RenderContext<'_, 'gc>) {
let state = self.0.read().state;
let current_state = self.get_state_child(state.into());
let current_state = self.get_state_child(self.0.state.get().into());
if let Some(state) = current_state {
state.render(context);
@ -664,8 +638,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
let mut bounds = *matrix * self.self_bounds();
// Add the bounds of the child, dictated by current state
let state = self.0.read().state;
if let Some(child) = self.get_state_child(state.into()) {
if let Some(child) = self.get_state_child(self.0.state.get().into()) {
let matrix = *matrix * *child.base().matrix();
let child_bounds = child.bounds_with_transform(&matrix);
bounds = bounds.union(&child_bounds);
@ -682,8 +655,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
) -> Rectangle<Twips> {
let mut bounds = *matrix * self.self_bounds();
let state = self.0.read().state;
if let Some(child) = self.get_state_child(state.into()) {
if let Some(child) = self.get_state_child(self.0.state.get().into()) {
let matrix = *matrix * *child.base().matrix();
bounds = bounds.union(&child.render_bounds_with_transform(&matrix, true, view_matrix));
}
@ -706,8 +678,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
options: HitTestOptions,
) -> bool {
if !options.contains(HitTestOptions::SKIP_INVISIBLE) || self.visible() {
let state = self.0.read().state;
if let Some(child) = self.get_state_child(state.into()) {
if let Some(child) = self.get_state_child(self.0.state.get().into()) {
//TODO: the if below should probably always be taken, why does the hit area
// sometimes have a parent?
let mut point = point;
@ -731,14 +702,15 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
fn object2(&self) -> Avm2Value<'gc> {
self.0
.read()
.object
.get()
.map(Avm2Value::from)
.unwrap_or(Avm2Value::Null)
}
fn set_object2(&self, context: &mut UpdateContext<'_, 'gc>, to: Avm2Object<'gc>) {
self.0.write(context.gc_context).object = Some(to);
let write = Gc::write(context.gc(), self.0);
unlock!(write, Avm2ButtonData, object).set(Some(to));
}
fn as_avm2_button(&self) -> Option<Self> {
@ -750,8 +722,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
}
fn allow_as_mask(&self) -> bool {
let state = self.0.read().state;
let current_state = self.get_state_child(state.into());
let current_state = self.get_state_child(self.0.state.get().into());
if let Some(current_state) = current_state.and_then(|cs| cs.as_container()) {
current_state.is_empty()
@ -766,21 +737,21 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
fn on_focus_changed(
&self,
context: &mut UpdateContext<'_, 'gc>,
_context: &mut UpdateContext<'_, 'gc>,
focused: bool,
_other: Option<DisplayObject<'gc>>,
) {
self.0.write(context.gc_context).has_focus = focused;
self.0.has_focus.set(focused);
}
}
impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> {
fn raw_interactive(&self) -> Ref<InteractiveObjectBase<'gc>> {
Ref::map(self.0.read(), |r| &r.base)
self.0.base.borrow()
}
fn raw_interactive_mut(&self, mc: &Mutation<'gc>) -> RefMut<InteractiveObjectBase<'gc>> {
RefMut::map(self.0.write(mc), |w| &mut w.base)
unlock!(Gc::write(mc, self.0), Avm2ButtonData, base).borrow_mut()
}
fn as_displayobject(self) -> DisplayObject<'gc> {
@ -809,8 +780,7 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> {
event: ClipEvent<'gc>,
) -> ClipEventResult {
if event.propagates() {
let state = self.0.read().state;
let current_state = self.get_state_child(state.into());
let current_state = self.get_state_child(self.0.state.get().into());
if let Some(current_state) = current_state.and_then(|s| s.as_interactive()) {
if current_state.handle_clip_event(context, event) == ClipEventResult::Handled {
@ -827,11 +797,8 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> {
context: &mut UpdateContext<'_, 'gc>,
event: ClipEvent<'gc>,
) -> ClipEventResult {
let write = self.0.write(context.gc_context);
// Translate the clip event to a button event, based on how the button state changes.
let static_data = write.static_data;
let static_data = static_data.read();
let static_data = self.0.static_data.cell.borrow();
let (new_state, sound) = match event {
ClipEvent::DragOut { .. } => (ButtonState::Over, None),
ClipEvent::DragOver { .. } => (ButtonState::Down, None),
@ -846,9 +813,8 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> {
_ => return ClipEventResult::NotHandled,
};
write.play_sound(context, sound);
let old_state = write.state;
drop(write);
self.0.play_sound(context, sound);
let old_state = self.0.state.get();
if old_state != new_state {
self.set_state(context, new_state);
@ -864,8 +830,7 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> {
) -> Avm2MousePick<'gc> {
// The button is hovered if the mouse is over any child nodes.
if self.visible() && self.mouse_enabled() {
let state = self.0.read().state;
let state_child = self.get_state_child(state.into());
let state_child = self.get_state_child(self.0.state.get().into());
if let Some(state_child) = state_child {
let mouse_pick = state_child
@ -878,8 +843,7 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> {
};
}
let hit_area = self.0.read().hit_area;
if let Some(hit_area) = hit_area {
if let Some(hit_area) = self.0.hit_area.get() {
//TODO: the if below should probably always be taken, why does the hit area
// sometimes have a parent?
if hit_area.parent().is_none() {
@ -922,17 +886,21 @@ impl<'gc> Avm2ButtonData<'gc> {
}
fn movie(&self) -> Arc<SwfMovie> {
self.static_data.read().swf.clone()
self.static_data.swf.clone()
}
}
/// Static data shared between all instances of a button.
#[allow(dead_code)]
#[derive(Clone, Debug, Collect)]
#[derive(Collect, Debug)]
#[collect(require_static)]
struct ButtonStatic {
swf: Arc<SwfMovie>,
id: CharacterId,
cell: RefCell<ButtonStaticMut>,
}
#[derive(Debug)]
struct ButtonStaticMut {
records: Vec<swf::ButtonRecord>,
/// The sounds to play on state changes for this button.

View File

@ -3835,7 +3835,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
button.set_sounds(button_sounds);
}
Some(Character::Avm2Button(button)) => {
button.set_sounds(context.gc_context, button_sounds);
button.set_sounds(button_sounds);
}
Some(_) => {
tracing::warn!(