Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/src/avm2/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub struct SystemClasses<'gc> {
pub textfield: ClassObject<'gc>,
pub textformat: ClassObject<'gc>,
pub graphics: ClassObject<'gc>,
pub loader: ClassObject<'gc>,
pub loaderinfo: ClassObject<'gc>,
pub bytearray: ClassObject<'gc>,
pub stage: ClassObject<'gc>,
Expand Down Expand Up @@ -167,6 +168,7 @@ impl<'gc> SystemClasses<'gc> {
textfield: object,
textformat: object,
graphics: object,
loader: object,
loaderinfo: object,
bytearray: object,
stage: object,
Expand Down Expand Up @@ -628,6 +630,7 @@ fn load_playerglobal<'gc>(
("flash.display", "Scene", scene),
("flash.display", "FrameLabel", framelabel),
("flash.display", "Graphics", graphics),
("flash.display", "Loader", loader),
("flash.display", "LoaderInfo", loaderinfo),
("flash.display", "MovieClip", movieclip),
("flash.display", "Shape", shape),
Expand Down
140 changes: 66 additions & 74 deletions core/src/avm2/globals/flash/display/bitmap.rs
Original file line number Diff line number Diff line change
@@ -1,109 +1,101 @@
//! `flash.display.Bitmap` builtin/prototype

use crate::avm2::activation::Activation;
use crate::avm2::globals::flash::display::bitmap_data::fill_bitmap_data_from_symbol;
use crate::avm2::object::{BitmapDataObject, Object, TObject};
use crate::avm2::value::Value;
use crate::avm2::Error;

use crate::avm2::globals::flash::display::bitmap_data::fill_bitmap_data_from_symbol;
use crate::bitmap::bitmap_data::BitmapData;
use crate::character::Character;
use crate::display_object::{Bitmap, TDisplayObject};
use crate::{avm2_stub_getter, avm2_stub_setter};
use gc_arena::GcCell;

pub fn assign_default_bitmap_data<'gc>(
activation: &mut Activation<'_, 'gc>,
bitmap: Bitmap<'gc>,
) -> Result<(), Error<'gc>> {
// We are being initialized by the movie. This means that we
// need to create bitmap data right away, since all AVM2 bitmaps
// hold bitmap data.
let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() {
Some(bd_class.construct(activation, &[])?)
} else if let Some(b_class) = bitmap.avm2_bitmap_class() {
// Instantiating Bitmap from a Flex-style bitmap asset.
// Contrary to the above comment, this code path DOES
// trigger from AVM2, since the DisplayObject instantiation
// logic does its job in this case.
if let Some((movie, symbol_id)) = activation
.context
.library
.avm2_class_registry()
.class_symbol(b_class)
{
if let Some(Character::Bitmap(bitmap)) = activation
.context
.library
.library_for_movie_mut(movie)
.character_by_id(symbol_id)
.cloned()
{
let new_bitmap_data =
GcCell::allocate(activation.context.gc_context, BitmapData::default());

fill_bitmap_data_from_symbol(activation, bitmap, new_bitmap_data);
Some(BitmapDataObject::from_bitmap_data(
activation,
new_bitmap_data,
activation.context.avm2.classes().bitmapdata,
)?)
} else {
//Class association not to a Bitmap
return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into());
}
} else {
//Class association not bidirectional
return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into());
}
} else {
// No class association
None
};

if let Some(bd_object) = bd_object.and_then(|bd| bd.as_bitmap_data()) {
bitmap.set_bitmap_data(&mut activation.context, bd_object);
}
Ok(())
}

/// Implements `flash.display.Bitmap`'s `init` method, which is called from the constructor
pub fn init<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(mut this) = this {
if let Some(this) = this {
activation.super_init(this, &[])?;

let bitmap_data = args
.get(0)
.cloned()
.unwrap_or(Value::Null)
.as_object()
.and_then(|v| v.as_object())
.and_then(|bd| bd.as_bitmap_data());
//TODO: Pixel snapping is not supported
let _pixel_snapping = args
.get(1)
.cloned()
.unwrap_or_else(|| "auto".into())
.coerce_to_string(activation)?;
let smoothing = args
.get(2)
.cloned()
.unwrap_or_else(|| false.into())
.coerce_to_boolean();
let smoothing = args.get(2).map(|v| v.coerce_to_boolean());

if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) {
//We are being initialized by the movie. This means that we
//need to create bitmap data right away, since all AVM2 bitmaps
//hold bitmap data.

let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() {
bd_class.construct(activation, &[])?
} else if let Some(b_class) = bitmap.avm2_bitmap_class() {
// Instantiating Bitmap from a Flex-style bitmap asset.
// Contrary to the above comment, this code path DOES
// trigger from AVM2, since the DisplayObject instantiation
// logic does its job in this case.
if let Some((movie, symbol_id)) = activation
.context
.library
.avm2_class_registry()
.class_symbol(b_class)
{
if let Some(Character::Bitmap(bitmap)) = activation
.context
.library
.library_for_movie_mut(movie)
.character_by_id(symbol_id)
.cloned()
{
let new_bitmap_data =
GcCell::allocate(activation.context.gc_context, BitmapData::default());

fill_bitmap_data_from_symbol(activation, bitmap, new_bitmap_data);
BitmapDataObject::from_bitmap_data(
activation,
new_bitmap_data,
activation.context.avm2.classes().bitmapdata,
)?
} else {
//Class association not to a Bitmap
return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into());
}
} else {
//Class association not bidirectional
return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into());
}
} else {
// No class association
return Err(
"Cannot instantiate Bitmap from timeline without associated symbol class"
.into(),
);
};

this.set_public_property("bitmapData", bd_object.into(), activation)?;

bitmap.set_smoothing(activation.context.gc_context, smoothing);
} else {
//We are being initialized by AVM2 (and aren't associated with a
//Bitmap subclass).

let bitmap_data = bitmap_data.unwrap_or_else(|| {
GcCell::allocate(activation.context.gc_context, BitmapData::dummy())
});

let bitmap =
Bitmap::new_with_bitmap_data(&mut activation.context, 0, bitmap_data, smoothing);

this.init_display_object(&mut activation.context, bitmap.into());
assign_default_bitmap_data(activation, bitmap)?;
if let Some(bitmap_data) = bitmap_data {
bitmap.set_bitmap_data(&mut activation.context, bitmap_data);
}
if let Some(smoothing) = smoothing {
bitmap.set_smoothing(activation.context.gc_context, smoothing);
}
}
}

Expand Down
135 changes: 103 additions & 32 deletions core/src/avm2/globals/flash/display/display_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,123 @@ use crate::avm2::activation::Activation;
use crate::avm2::filters::FilterAvm2Ext;
use crate::avm2::object::{Object, TObject};
use crate::avm2::value::Value;
use crate::avm2::Error;
use crate::avm2::Multiname;
use crate::avm2::Namespace;
use crate::avm2::{ArrayObject, ArrayStorage};
use crate::display_object::{DisplayObject, HitTestOptions, TDisplayObject};
use crate::avm2::{ClassObject, Error};
use crate::bitmap::bitmap_data::BitmapData;
use crate::display_object::{
Avm2Button, Bitmap, DisplayObject, EditText, Graphic, HitTestOptions, LoaderDisplay, MovieClip,
TDisplayObject,
};
use crate::ecma_conversions::round_to_even;
use crate::frame_lifecycle::catchup_display_object_to_frame;
use crate::prelude::*;
use crate::string::AvmString;
use crate::types::{Degrees, Percent};
use crate::vminterface::Instantiator;
use crate::{avm2_stub_getter, avm2_stub_setter};
use gc_arena::GcCell;
use ruffle_render::filters::Filter;
use std::str::FromStr;
use std::sync::Arc;
use swf::BlendMode;

pub use crate::avm2::object::stage_allocator as display_object_allocator;
use crate::tag_utils::SwfMovie;

fn create_display_object<'gc>(
activation: &mut Activation<'_, 'gc>,
class_object: ClassObject<'gc>,
) -> Result<(DisplayObject<'gc>, bool, bool), Error<'gc>> {
// Iterate the inheritance chain, starting from `this` and working backwards through `super`s
// This accounts for the cases where a super may be linked to symbol, but `this` may not be
let mut current_class = Some(class_object);
while let Some(class) = current_class {
if let Some((movie, symbol)) = activation
.context
.library
.avm2_class_registry()
.class_symbol(class)
{
let display_object = activation
.context
.library
.library_for_movie_mut(movie)
.instantiate_by_id(symbol, activation.context.gc_context)?;

return Ok((display_object, true, true));
}
current_class = class.superclass_object();
}

if class_object.has_class_in_chain(activation.avm2().classes().loader) {
return Ok((
LoaderDisplay::new_with_avm2(
activation.context.gc_context,
activation.context.swf.clone(),
)
.into(),
false,
false,
));
}

if class_object.has_class_in_chain(activation.avm2().classes().simplebutton) {
return Ok((
Avm2Button::empty_button(&mut activation.context).into(),
true,
false,
));
}

if class_object.has_class_in_chain(activation.avm2().classes().bitmap) {
let bitmap_data = GcCell::allocate(activation.context.gc_context, BitmapData::dummy());
let bitmap = Bitmap::new_with_bitmap_data(&mut activation.context, 0, bitmap_data, false);
return Ok((bitmap.into(), false, false));
}

if class_object.has_class_in_chain(activation.avm2().classes().shape) {
return Ok((
Graphic::new_with_avm2(&mut activation.context).into(),
false,
false,
));
}

if class_object.has_class_in_chain(activation.avm2().classes().textfield) {
let movie = Arc::new(SwfMovie::empty(activation.context.swf.version()));
let edit_text = EditText::new(&mut activation.context, movie, 0.0, 0.0, 100.0, 100.0);
return Ok((edit_text.into(), false, false));
}

let movie = Arc::new(SwfMovie::empty(activation.context.swf.version()));
return Ok((
MovieClip::new_with_avm2(movie, None, class_object, activation.context.gc_context).into(),
false,
false,
));
}

pub fn setup_display_object<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
) -> Result<DisplayObject<'gc>, Error<'gc>> {
let (display_object, post_instantiate, catchup) = create_display_object(
activation,
this.instance_of()
.expect("Something that inherits DisplayObject should have a class"),
)?;
this.init_display_object(&mut activation.context, display_object);
display_object.set_object2(&mut activation.context, this);
if post_instantiate {
display_object.post_instantiation(&mut activation.context, None, Instantiator::Avm2, false);
}
if catchup {
catchup_display_object_to_frame(&mut activation.context, display_object);
}
Ok(display_object)
}

/// Implements `flash.display.DisplayObject`'s native instance constructor.
pub fn native_instance_init<'gc>(
Expand All @@ -32,36 +132,7 @@ pub fn native_instance_init<'gc>(
activation.super_init(this, &[])?;

if this.as_display_object().is_none() {
let mut class_object = this.instance_of();

// Iterate the inheritance chain, starting from `this` and working backwards through `super`s
// This accounts for the cases where a super may be linked to symbol, but `this` may not be
while let Some(class) = class_object {
if let Some((movie, symbol)) = activation
.context
.library
.avm2_class_registry()
.class_symbol(class)
{
let child = activation
.context
.library
.library_for_movie_mut(movie)
.instantiate_by_id(symbol, activation.context.gc_context)?;

this.init_display_object(&mut activation.context, child);

child.post_instantiation(
&mut activation.context,
None,
Instantiator::Avm2,
false,
);
catchup_display_object_to_frame(&mut activation.context, child);
break;
}
class_object = class.superclass_object();
}
setup_display_object(activation, this)?;
}

if let Some(dobj) = this.as_display_object() {
Expand Down
14 changes: 3 additions & 11 deletions core/src/avm2/globals/flash/display/display_object_container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use crate::avm2::activation::Activation;
use crate::avm2::error::range_error;
use crate::avm2::globals::flash::display::sprite::init_empty_sprite;
use crate::avm2::object::{Object, TObject};
use crate::avm2::value::Value;
use crate::avm2::{ArrayObject, ArrayStorage, Error};
Expand Down Expand Up @@ -182,17 +181,10 @@ pub fn add_child<'gc>(
.unwrap_or(Value::Undefined)
.as_object()
.ok_or("ArgumentError: Child not a valid display object")?;
if child.as_display_object().is_none() {
let sprite = activation.avm2().classes().sprite;
if child.is_of_type(sprite, activation) {
// [NA] Hack to make Haxe work - they call addChild before super()
// This will create an empty sprite the same way sprite's constructor will.
init_empty_sprite(activation, child)?;
}
}
let child = child
.as_display_object()
.ok_or("ArgumentError: Child not a valid display object")?;
.as_stage_object()
.ok_or("ArgumentError: Child not a valid display object")?
.get_or_create_display_object(activation)?;
let target_index = ctr.num_children();

validate_add_operation(activation, parent, child, target_index)?;
Expand Down
Loading