avm2: Use inner mutability instead of `GcCell` in `Avm2Button`
This commit is contained in:
parent
d1cbe46e25
commit
e014d7c023
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
Avm2Button(Gc::new(
|
||||
context.gc(),
|
||||
Avm2ButtonData {
|
||||
base: Default::default(),
|
||||
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,
|
||||
};
|
||||
|
||||
Avm2Button(GcCell::new(
|
||||
context.gc_context,
|
||||
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 {
|
||||
}),
|
||||
},
|
||||
),
|
||||
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.
|
||||
|
|
|
@ -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!(
|
||||
|
|
Loading…
Reference in New Issue