2020-12-09 00:08:08 +00:00
|
|
|
use crate::avm1::{
|
|
|
|
Error as Avm1Error, Object as Avm1Object, TObject as Avm1TObject, Value as Avm1Value,
|
|
|
|
};
|
2021-04-10 02:30:54 +00:00
|
|
|
use crate::avm2::{
|
|
|
|
Avm2, Event as Avm2Event, Object as Avm2Object, TObject as Avm2TObject, Value as Avm2Value,
|
|
|
|
};
|
2019-10-27 19:46:23 +00:00
|
|
|
use crate::context::{RenderContext, UpdateContext};
|
2021-02-27 02:09:27 +00:00
|
|
|
use crate::drawing::Drawing;
|
2019-10-27 19:46:23 +00:00
|
|
|
use crate::player::NEWEST_PLAYER_VERSION;
|
2019-04-29 05:55:44 +00:00
|
|
|
use crate::prelude::*;
|
2019-11-14 02:41:38 +00:00
|
|
|
use crate::tag_utils::SwfMovie;
|
2019-04-29 05:55:44 +00:00
|
|
|
use crate::transform::Transform;
|
2020-09-25 23:18:31 +00:00
|
|
|
use crate::types::{Degrees, Percent};
|
2021-02-03 03:19:24 +00:00
|
|
|
use crate::vminterface::{AvmType, Instantiator};
|
2021-01-22 00:35:46 +00:00
|
|
|
use bitflags::bitflags;
|
2019-12-07 02:29:36 +00:00
|
|
|
use gc_arena::{Collect, MutationContext};
|
|
|
|
use ruffle_macros::enum_trait_object;
|
|
|
|
use std::cell::{Ref, RefMut};
|
2019-08-28 23:29:43 +00:00
|
|
|
use std::fmt::Debug;
|
2019-11-14 02:41:38 +00:00
|
|
|
use std::sync::Arc;
|
2021-04-18 06:32:53 +00:00
|
|
|
use swf::Fixed8;
|
2019-04-25 17:52:22 +00:00
|
|
|
|
2021-04-23 01:10:29 +00:00
|
|
|
mod avm1_button;
|
2021-04-24 01:16:27 +00:00
|
|
|
mod avm2_button;
|
2019-10-26 22:04:52 +00:00
|
|
|
mod bitmap;
|
2020-11-03 21:46:11 +00:00
|
|
|
mod container;
|
2019-10-26 22:04:52 +00:00
|
|
|
mod edit_text;
|
|
|
|
mod graphic;
|
|
|
|
mod morph_shape;
|
|
|
|
mod movie_clip;
|
2021-04-13 02:10:18 +00:00
|
|
|
mod stage;
|
2019-10-26 22:04:52 +00:00
|
|
|
mod text;
|
2020-10-15 04:12:31 +00:00
|
|
|
mod video;
|
2019-10-26 22:04:52 +00:00
|
|
|
|
2020-07-01 22:09:43 +00:00
|
|
|
use crate::avm1::activation::Activation;
|
2021-01-31 00:36:45 +00:00
|
|
|
use crate::backend::ui::MouseCursor;
|
2020-11-15 01:12:00 +00:00
|
|
|
pub use crate::display_object::container::{
|
|
|
|
DisplayObjectContainer, Lists, TDisplayObjectContainer,
|
|
|
|
};
|
2020-05-23 23:51:40 +00:00
|
|
|
use crate::events::{ClipEvent, ClipEventResult};
|
2021-04-23 01:10:29 +00:00
|
|
|
pub use avm1_button::Avm1Button;
|
2021-04-24 01:16:27 +00:00
|
|
|
pub use avm2_button::Avm2Button;
|
2019-10-26 22:04:52 +00:00
|
|
|
pub use bitmap::Bitmap;
|
2020-10-30 22:13:07 +00:00
|
|
|
pub use edit_text::{AutoSizeMode, EditText, TextSelection};
|
2019-10-26 22:04:52 +00:00
|
|
|
pub use graphic::Graphic;
|
|
|
|
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
2020-09-19 03:09:27 +00:00
|
|
|
pub use movie_clip::{MovieClip, Scene};
|
2021-04-25 22:17:26 +00:00
|
|
|
pub use stage::{Stage, StageAlign, StageScaleMode};
|
2019-10-26 22:04:52 +00:00
|
|
|
pub use text::Text;
|
2020-10-16 03:25:32 +00:00
|
|
|
pub use video::Video;
|
2019-10-26 22:04:52 +00:00
|
|
|
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Clone, Debug, Collect)]
|
|
|
|
#[collect(no_drop)]
|
2019-08-13 02:00:12 +00:00
|
|
|
pub struct DisplayObjectBase<'gc> {
|
2019-12-07 02:29:36 +00:00
|
|
|
parent: Option<DisplayObject<'gc>>,
|
2019-09-16 16:13:12 +00:00
|
|
|
place_frame: u16,
|
2019-04-29 05:55:44 +00:00
|
|
|
depth: Depth,
|
|
|
|
transform: Transform,
|
2019-05-06 18:15:52 +00:00
|
|
|
name: String,
|
2019-05-12 17:48:00 +00:00
|
|
|
clip_depth: Depth,
|
2019-10-18 04:10:13 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
// Cached transform properties `_xscale`, `_yscale`, `_rotation`.
|
2020-09-25 03:17:07 +00:00
|
|
|
// These are expensive to calculate, so they will be calculated and cached
|
|
|
|
// when AS requests one of these properties.
|
|
|
|
//
|
|
|
|
// `_xscale` and `_yscale` are stored in units of percentages to avoid
|
|
|
|
// floating-point precision errors with movies that accumulate onto the
|
|
|
|
// scale parameters.
|
2020-09-25 23:18:31 +00:00
|
|
|
rotation: Degrees,
|
2020-09-25 03:17:07 +00:00
|
|
|
scale_x: Percent,
|
|
|
|
scale_y: Percent,
|
2019-12-04 01:52:00 +00:00
|
|
|
skew: f64,
|
|
|
|
|
2019-10-18 04:10:13 +00:00
|
|
|
/// The previous sibling of this display object in order of execution.
|
2019-12-07 02:29:36 +00:00
|
|
|
prev_sibling: Option<DisplayObject<'gc>>,
|
2019-10-18 04:10:13 +00:00
|
|
|
|
|
|
|
/// The next sibling of this display object in order of execution.
|
2019-12-07 02:29:36 +00:00
|
|
|
next_sibling: Option<DisplayObject<'gc>>,
|
2019-10-18 04:10:13 +00:00
|
|
|
|
2021-01-24 08:16:07 +00:00
|
|
|
/// The sound transform of sounds playing via this display object.
|
|
|
|
sound_transform: SoundTransform,
|
|
|
|
|
2020-11-04 07:30:48 +00:00
|
|
|
/// The display object that we are being masked by.
|
|
|
|
masker: Option<DisplayObject<'gc>>,
|
|
|
|
|
|
|
|
/// The display object we are currently masking.
|
|
|
|
maskee: Option<DisplayObject<'gc>>,
|
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
/// Bit flags for various display object properites.
|
2021-01-22 00:35:46 +00:00
|
|
|
flags: DisplayObjectFlags,
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
|
|
|
|
2019-10-13 21:27:04 +00:00
|
|
|
impl<'gc> Default for DisplayObjectBase<'gc> {
|
|
|
|
fn default() -> Self {
|
2019-04-29 05:55:44 +00:00
|
|
|
Self {
|
2019-08-13 02:00:12 +00:00
|
|
|
parent: Default::default(),
|
2019-09-16 16:13:12 +00:00
|
|
|
place_frame: Default::default(),
|
2019-04-29 05:55:44 +00:00
|
|
|
depth: Default::default(),
|
|
|
|
transform: Default::default(),
|
2019-05-06 18:15:52 +00:00
|
|
|
name: Default::default(),
|
2019-05-12 17:48:00 +00:00
|
|
|
clip_depth: Default::default(),
|
2020-09-25 23:18:31 +00:00
|
|
|
rotation: Degrees::from_radians(0.0),
|
2020-09-25 03:17:07 +00:00
|
|
|
scale_x: Percent::from_unit(1.0),
|
|
|
|
scale_y: Percent::from_unit(1.0),
|
2019-12-04 01:52:00 +00:00
|
|
|
skew: 0.0,
|
2019-10-18 04:10:13 +00:00
|
|
|
prev_sibling: None,
|
|
|
|
next_sibling: None,
|
2020-11-04 07:30:48 +00:00
|
|
|
masker: None,
|
|
|
|
maskee: None,
|
2021-01-24 08:16:07 +00:00
|
|
|
sound_transform: Default::default(),
|
2021-01-22 00:35:46 +00:00
|
|
|
flags: DisplayObjectFlags::VISIBLE,
|
2019-04-26 03:27:44 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-29 05:55:44 +00:00
|
|
|
}
|
2019-04-26 03:27:44 +00:00
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
#[allow(dead_code)]
|
|
|
|
impl<'gc> DisplayObjectBase<'gc> {
|
2020-02-02 01:19:44 +00:00
|
|
|
/// Reset all properties that would be adjusted by a movie load.
|
|
|
|
fn reset_for_movie_load(&mut self) {
|
2021-01-22 00:35:46 +00:00
|
|
|
let flags_to_keep = self.flags & DisplayObjectFlags::LOCK_ROOT;
|
|
|
|
self.flags = flags_to_keep | DisplayObjectFlags::VISIBLE;
|
2020-02-02 01:19:44 +00:00
|
|
|
}
|
|
|
|
|
2019-09-15 18:34:30 +00:00
|
|
|
fn id(&self) -> CharacterId {
|
|
|
|
0
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-09-08 02:33:06 +00:00
|
|
|
fn depth(&self) -> Depth {
|
|
|
|
self.depth
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-17 09:51:00 +00:00
|
|
|
fn set_depth(&mut self, depth: Depth) {
|
|
|
|
self.depth = depth;
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-09-16 16:13:12 +00:00
|
|
|
fn place_frame(&self) -> u16 {
|
|
|
|
self.place_frame
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_place_frame(&mut self, frame: u16) {
|
2019-09-16 16:13:12 +00:00
|
|
|
self.place_frame = frame;
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-05-01 16:55:54 +00:00
|
|
|
fn transform(&self) -> &Transform {
|
|
|
|
&self.transform
|
|
|
|
}
|
|
|
|
|
2019-08-14 18:59:07 +00:00
|
|
|
fn matrix(&self) -> &Matrix {
|
2019-04-29 05:55:44 +00:00
|
|
|
&self.transform.matrix
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn matrix_mut(&mut self) -> &mut Matrix {
|
2019-08-15 21:42:45 +00:00
|
|
|
&mut self.transform.matrix
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_matrix(&mut self, matrix: &Matrix) {
|
2019-05-03 18:44:12 +00:00
|
|
|
self.transform.matrix = *matrix;
|
2021-01-22 00:35:46 +00:00
|
|
|
self.flags -= DisplayObjectFlags::SCALE_ROTATION_CACHED;
|
2019-04-26 03:27:44 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-08-14 18:59:07 +00:00
|
|
|
fn color_transform(&self) -> &ColorTransform {
|
2019-04-29 05:55:44 +00:00
|
|
|
&self.transform.color_transform
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn color_transform_mut(&mut self) -> &mut ColorTransform {
|
|
|
|
&mut self.transform.color_transform
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_color_transform(&mut self, color_transform: &ColorTransform) {
|
2019-05-03 18:44:12 +00:00
|
|
|
self.transform.color_transform = *color_transform;
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn x(&self) -> f64 {
|
2020-02-18 19:39:53 +00:00
|
|
|
self.transform.matrix.tx.to_pixels()
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn set_x(&mut self, value: f64) {
|
2019-12-15 17:32:42 +00:00
|
|
|
self.set_transformed_by_script(true);
|
2020-02-18 19:39:53 +00:00
|
|
|
self.transform.matrix.tx = Twips::from_pixels(value)
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn y(&self) -> f64 {
|
2020-02-18 19:39:53 +00:00
|
|
|
self.transform.matrix.ty.to_pixels()
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn set_y(&mut self, value: f64) {
|
2019-12-15 17:32:42 +00:00
|
|
|
self.set_transformed_by_script(true);
|
2020-02-18 19:39:53 +00:00
|
|
|
self.transform.matrix.ty = Twips::from_pixels(value)
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Caches the scale and rotation factors for this display object, if necessary.
|
|
|
|
/// Calculating these requires heavy trig ops, so we only do it when `_xscale`, `_yscale` or
|
|
|
|
/// `_rotation` is accessed.
|
|
|
|
fn cache_scale_rotation(&mut self) {
|
2021-01-22 00:35:46 +00:00
|
|
|
if !self
|
|
|
|
.flags
|
|
|
|
.contains(DisplayObjectFlags::SCALE_ROTATION_CACHED)
|
|
|
|
{
|
2019-12-04 01:52:00 +00:00
|
|
|
let (a, b, c, d) = (
|
2020-11-21 04:32:07 +00:00
|
|
|
f64::from(self.transform.matrix.a),
|
|
|
|
f64::from(self.transform.matrix.b),
|
|
|
|
f64::from(self.transform.matrix.c),
|
|
|
|
f64::from(self.transform.matrix.d),
|
2019-12-04 01:52:00 +00:00
|
|
|
);
|
|
|
|
// If this object's transform matrix is:
|
|
|
|
// [[a c tx]
|
|
|
|
// [b d ty]]
|
|
|
|
// After transformation, the X-axis and Y-axis will turn into the column vectors x' = <a, b> and y' = <c, d>.
|
|
|
|
// We derive the scale, rotation, and skew values from these transformed axes.
|
|
|
|
// The skew value is not exposed by ActionScript, but is remembered internally.
|
|
|
|
// xscale = len(x')
|
|
|
|
// yscale = len(y')
|
|
|
|
// rotation = atan2(b, a) (the rotation of x' from the normal x-axis).
|
|
|
|
// skew = atan2(-c, d) - atan2(b, a) (the signed difference between y' and x' rotation)
|
|
|
|
|
|
|
|
// This can produce some surprising results due to the overlap between flipping/rotation/skewing.
|
|
|
|
// For example, in Flash, using Modify->Transform->Flip Horizontal and then tracing _xscale, _yscale, and _rotation
|
|
|
|
// will output 100, 100, and 180. (a horizontal flip could also be a 180 degree skew followed by 180 degree rotation!)
|
2020-11-21 04:32:07 +00:00
|
|
|
let rotation_x = f64::atan2(b, a);
|
|
|
|
let rotation_y = f64::atan2(-c, d);
|
|
|
|
let scale_x = f64::sqrt(a * a + b * b);
|
|
|
|
let scale_y = f64::sqrt(c * c + d * d);
|
|
|
|
self.rotation = Degrees::from_radians(rotation_x);
|
|
|
|
self.scale_x = Percent::from_unit(scale_x);
|
|
|
|
self.scale_y = Percent::from_unit(scale_y);
|
|
|
|
self.skew = rotation_y - rotation_x;
|
2021-01-22 00:35:46 +00:00
|
|
|
self.flags |= DisplayObjectFlags::SCALE_ROTATION_CACHED;
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_scale(&mut self, scale_x: f32, scale_y: f32, rotation: f32) {
|
|
|
|
self.cache_scale_rotation();
|
|
|
|
let mut matrix = &mut self.transform.matrix;
|
|
|
|
let rotation = rotation.to_radians();
|
|
|
|
let cos_x = f32::cos(rotation);
|
|
|
|
let sin_x = f32::sin(rotation);
|
2020-09-25 03:17:07 +00:00
|
|
|
self.scale_x = Percent::from_unit(scale_x.into());
|
|
|
|
self.scale_y = Percent::from_unit(scale_y.into());
|
2020-09-25 23:18:31 +00:00
|
|
|
self.rotation = Degrees::from_radians(rotation.into());
|
2019-12-04 01:52:00 +00:00
|
|
|
matrix.a = (scale_x * cos_x) as f32;
|
|
|
|
matrix.b = (scale_x * sin_x) as f32;
|
|
|
|
matrix.c = (scale_y * -sin_x) as f32;
|
|
|
|
matrix.d = (scale_y * cos_x) as f32;
|
|
|
|
}
|
|
|
|
|
2020-09-25 23:18:31 +00:00
|
|
|
fn rotation(&mut self) -> Degrees {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.cache_scale_rotation();
|
|
|
|
self.rotation
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2020-09-25 23:18:31 +00:00
|
|
|
fn set_rotation(&mut self, degrees: Degrees) {
|
2019-12-15 17:32:42 +00:00
|
|
|
self.set_transformed_by_script(true);
|
2019-12-04 01:52:00 +00:00
|
|
|
self.cache_scale_rotation();
|
2020-09-25 23:18:31 +00:00
|
|
|
self.rotation = degrees;
|
|
|
|
let cos_x = f64::cos(degrees.into_radians());
|
|
|
|
let sin_x = f64::sin(degrees.into_radians());
|
|
|
|
let cos_y = f64::cos(degrees.into_radians() + self.skew);
|
|
|
|
let sin_y = f64::sin(degrees.into_radians() + self.skew);
|
2019-12-04 01:52:00 +00:00
|
|
|
let mut matrix = &mut self.transform.matrix;
|
2020-09-25 03:17:07 +00:00
|
|
|
matrix.a = (self.scale_x.into_unit() * cos_x) as f32;
|
|
|
|
matrix.b = (self.scale_x.into_unit() * sin_x) as f32;
|
|
|
|
matrix.c = (self.scale_y.into_unit() * -sin_y) as f32;
|
|
|
|
matrix.d = (self.scale_y.into_unit() * cos_y) as f32;
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2020-09-25 03:17:07 +00:00
|
|
|
fn scale_x(&mut self) -> Percent {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.cache_scale_rotation();
|
|
|
|
self.scale_x
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2020-09-25 03:17:07 +00:00
|
|
|
fn set_scale_x(&mut self, value: Percent) {
|
2019-12-15 17:32:42 +00:00
|
|
|
self.set_transformed_by_script(true);
|
2019-12-04 01:52:00 +00:00
|
|
|
self.cache_scale_rotation();
|
|
|
|
self.scale_x = value;
|
2020-09-25 23:18:31 +00:00
|
|
|
let cos = f64::cos(self.rotation.into_radians());
|
|
|
|
let sin = f64::sin(self.rotation.into_radians());
|
2019-12-04 01:52:00 +00:00
|
|
|
let mut matrix = &mut self.transform.matrix;
|
2020-09-25 03:17:07 +00:00
|
|
|
matrix.a = (cos * value.into_unit()) as f32;
|
|
|
|
matrix.b = (sin * value.into_unit()) as f32;
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2020-09-25 03:17:07 +00:00
|
|
|
fn scale_y(&mut self) -> Percent {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.cache_scale_rotation();
|
|
|
|
self.scale_y
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2020-09-25 03:17:07 +00:00
|
|
|
fn set_scale_y(&mut self, value: Percent) {
|
2019-12-15 17:32:42 +00:00
|
|
|
self.set_transformed_by_script(true);
|
2019-12-04 01:52:00 +00:00
|
|
|
self.cache_scale_rotation();
|
|
|
|
self.scale_y = value;
|
2020-09-25 23:18:31 +00:00
|
|
|
let cos = f64::cos(self.rotation.into_radians() + self.skew);
|
|
|
|
let sin = f64::sin(self.rotation.into_radians() + self.skew);
|
2019-12-04 01:52:00 +00:00
|
|
|
let mut matrix = &mut self.transform.matrix;
|
2020-09-25 03:17:07 +00:00
|
|
|
matrix.c = (-sin * value.into_unit()) as f32;
|
|
|
|
matrix.d = (cos * value.into_unit()) as f32;
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
|
|
|
|
2019-05-06 18:15:52 +00:00
|
|
|
fn name(&self) -> &str {
|
|
|
|
&self.name
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_name(&mut self, name: &str) {
|
2019-05-06 18:15:52 +00:00
|
|
|
self.name = name.to_string();
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn alpha(&self) -> f64 {
|
|
|
|
f64::from(self.color_transform().a_mult)
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn set_alpha(&mut self, value: f64) {
|
2019-12-15 17:32:42 +00:00
|
|
|
self.set_transformed_by_script(true);
|
2021-04-18 06:32:53 +00:00
|
|
|
self.color_transform_mut().a_mult = Fixed8::from_f64(value)
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-05-12 17:48:00 +00:00
|
|
|
fn clip_depth(&self) -> Depth {
|
|
|
|
self.clip_depth
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_clip_depth(&mut self, depth: Depth) {
|
2019-05-12 17:48:00 +00:00
|
|
|
self.clip_depth = depth;
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-05-05 02:14:19 +00:00
|
|
|
fn parent(&self) -> Option<DisplayObject<'gc>> {
|
2019-08-13 02:00:12 +00:00
|
|
|
self.parent
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_parent(&mut self, parent: Option<DisplayObject<'gc>>) {
|
2019-08-13 02:00:12 +00:00
|
|
|
self.parent = parent;
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn prev_sibling(&self) -> Option<DisplayObject<'gc>> {
|
2019-10-18 04:10:13 +00:00
|
|
|
self.prev_sibling
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_prev_sibling(&mut self, node: Option<DisplayObject<'gc>>) {
|
2019-10-18 04:10:13 +00:00
|
|
|
self.prev_sibling = node;
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn next_sibling(&self) -> Option<DisplayObject<'gc>> {
|
2019-10-18 04:10:13 +00:00
|
|
|
self.next_sibling
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_next_sibling(&mut self, node: Option<DisplayObject<'gc>>) {
|
2019-10-18 04:10:13 +00:00
|
|
|
self.next_sibling = node;
|
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-10-18 04:10:13 +00:00
|
|
|
fn removed(&self) -> bool {
|
2021-01-22 00:35:46 +00:00
|
|
|
self.flags.contains(DisplayObjectFlags::REMOVED)
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-01-22 00:35:46 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn set_removed(&mut self, value: bool) {
|
2021-03-06 12:26:19 +00:00
|
|
|
self.flags.set(DisplayObjectFlags::REMOVED, value);
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2019-12-04 01:52:00 +00:00
|
|
|
|
2021-01-24 08:16:07 +00:00
|
|
|
fn sound_transform(&self) -> &SoundTransform {
|
|
|
|
&self.sound_transform
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_sound_transform(&mut self, sound_transform: SoundTransform) {
|
|
|
|
self.sound_transform = sound_transform;
|
|
|
|
}
|
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn visible(&self) -> bool {
|
2021-01-22 00:35:46 +00:00
|
|
|
self.flags.contains(DisplayObjectFlags::VISIBLE)
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2019-12-15 17:36:58 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn set_visible(&mut self, value: bool) {
|
2021-03-06 12:26:19 +00:00
|
|
|
self.flags.set(DisplayObjectFlags::VISIBLE, value);
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2019-12-15 17:32:42 +00:00
|
|
|
|
2020-12-01 02:50:50 +00:00
|
|
|
fn lock_root(&self) -> bool {
|
2021-01-22 00:35:46 +00:00
|
|
|
self.flags.contains(DisplayObjectFlags::LOCK_ROOT)
|
2020-12-01 02:50:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set_lock_root(&mut self, value: bool) {
|
2021-03-06 12:26:19 +00:00
|
|
|
self.flags.set(DisplayObjectFlags::LOCK_ROOT, value);
|
2020-12-01 02:50:50 +00:00
|
|
|
}
|
|
|
|
|
2019-12-15 17:32:42 +00:00
|
|
|
fn transformed_by_script(&self) -> bool {
|
2021-01-22 00:35:46 +00:00
|
|
|
self.flags
|
|
|
|
.contains(DisplayObjectFlags::TRANSFORMED_BY_SCRIPT)
|
2019-12-15 17:32:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set_transformed_by_script(&mut self, value: bool) {
|
2021-03-06 12:26:19 +00:00
|
|
|
self.flags
|
|
|
|
.set(DisplayObjectFlags::TRANSFORMED_BY_SCRIPT, value);
|
2019-12-15 17:32:42 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 02:19:30 +00:00
|
|
|
fn placed_by_script(&self) -> bool {
|
2021-01-22 00:35:46 +00:00
|
|
|
self.flags.contains(DisplayObjectFlags::PLACED_BY_SCRIPT)
|
2020-10-24 02:19:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set_placed_by_script(&mut self, value: bool) {
|
2021-03-06 12:26:19 +00:00
|
|
|
self.flags.set(DisplayObjectFlags::PLACED_BY_SCRIPT, value);
|
2020-10-24 02:19:30 +00:00
|
|
|
}
|
|
|
|
|
2020-11-28 19:15:30 +00:00
|
|
|
fn instantiated_by_timeline(&self) -> bool {
|
|
|
|
self.flags
|
2021-01-22 00:35:46 +00:00
|
|
|
.contains(DisplayObjectFlags::INSTANTIATED_BY_TIMELINE)
|
2020-11-28 19:15:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set_instantiated_by_timeline(&mut self, value: bool) {
|
2021-03-06 12:26:19 +00:00
|
|
|
self.flags
|
|
|
|
.set(DisplayObjectFlags::INSTANTIATED_BY_TIMELINE, value);
|
2020-11-28 19:15:30 +00:00
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn swf_version(&self) -> u8 {
|
|
|
|
self.parent
|
|
|
|
.map(|p| p.swf_version())
|
|
|
|
.unwrap_or(NEWEST_PLAYER_VERSION)
|
2019-05-09 01:10:43 +00:00
|
|
|
}
|
2019-11-14 02:41:38 +00:00
|
|
|
|
|
|
|
fn movie(&self) -> Option<Arc<SwfMovie>> {
|
|
|
|
self.parent.and_then(|p| p.movie())
|
|
|
|
}
|
2020-11-04 07:30:48 +00:00
|
|
|
|
|
|
|
fn masker(&self) -> Option<DisplayObject<'gc>> {
|
|
|
|
self.masker
|
|
|
|
}
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_masker(&mut self, node: Option<DisplayObject<'gc>>) {
|
2020-11-04 07:30:48 +00:00
|
|
|
self.masker = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn maskee(&self) -> Option<DisplayObject<'gc>> {
|
|
|
|
self.maskee
|
|
|
|
}
|
2021-02-05 16:04:23 +00:00
|
|
|
fn set_maskee(&mut self, node: Option<DisplayObject<'gc>>) {
|
2020-11-04 07:30:48 +00:00
|
|
|
self.maskee = node;
|
|
|
|
}
|
2019-04-29 05:55:44 +00:00
|
|
|
}
|
|
|
|
|
2021-04-16 21:55:57 +00:00
|
|
|
pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_, 'gc>) {
|
|
|
|
if this.maskee().is_some() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
context.transform_stack.push(&*this.transform());
|
|
|
|
|
|
|
|
let mask = this.masker();
|
|
|
|
let mut mask_transform = crate::transform::Transform::default();
|
|
|
|
if let Some(m) = mask {
|
|
|
|
mask_transform.matrix = this.global_to_local_matrix();
|
|
|
|
mask_transform.matrix *= m.local_to_global_matrix();
|
|
|
|
context.renderer.push_mask();
|
|
|
|
context.allow_mask = false;
|
|
|
|
context.transform_stack.push(&mask_transform);
|
|
|
|
m.render_self(context);
|
|
|
|
context.transform_stack.pop();
|
|
|
|
context.allow_mask = true;
|
|
|
|
context.renderer.activate_mask();
|
|
|
|
}
|
|
|
|
this.render_self(context);
|
|
|
|
if let Some(m) = mask {
|
|
|
|
context.renderer.deactivate_mask();
|
|
|
|
context.allow_mask = false;
|
|
|
|
context.transform_stack.push(&mask_transform);
|
|
|
|
m.render_self(context);
|
|
|
|
context.transform_stack.pop();
|
|
|
|
context.allow_mask = true;
|
|
|
|
context.renderer.pop_mask();
|
|
|
|
}
|
|
|
|
|
|
|
|
context.transform_stack.pop();
|
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
#[enum_trait_object(
|
|
|
|
#[derive(Clone, Collect, Debug, Copy)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub enum DisplayObject<'gc> {
|
2021-04-13 02:10:18 +00:00
|
|
|
Stage(Stage<'gc>),
|
2019-12-07 02:29:36 +00:00
|
|
|
Bitmap(Bitmap<'gc>),
|
2021-04-23 01:10:29 +00:00
|
|
|
Avm1Button(Avm1Button<'gc>),
|
2021-04-24 01:16:27 +00:00
|
|
|
Avm2Button(Avm2Button<'gc>),
|
2019-12-07 02:29:36 +00:00
|
|
|
EditText(EditText<'gc>),
|
|
|
|
Graphic(Graphic<'gc>),
|
|
|
|
MorphShape(MorphShape<'gc>),
|
|
|
|
MovieClip(MovieClip<'gc>),
|
|
|
|
Text(Text<'gc>),
|
2020-10-15 04:12:31 +00:00
|
|
|
Video(Video<'gc>),
|
2019-12-07 02:29:36 +00:00
|
|
|
}
|
|
|
|
)]
|
2020-09-04 07:41:08 +00:00
|
|
|
pub trait TDisplayObject<'gc>:
|
|
|
|
'gc + Clone + Copy + Collect + Debug + Into<DisplayObject<'gc>>
|
|
|
|
{
|
2019-09-15 18:34:30 +00:00
|
|
|
fn id(&self) -> CharacterId;
|
2019-09-08 02:33:06 +00:00
|
|
|
fn depth(&self) -> Depth;
|
2019-12-17 09:51:00 +00:00
|
|
|
fn set_depth(&self, gc_context: MutationContext<'gc, '_>, depth: Depth);
|
2019-12-04 01:52:00 +00:00
|
|
|
|
2020-02-21 20:57:45 +00:00
|
|
|
/// The untransformed inherent bounding box of this object.
|
|
|
|
/// These bounds do **not** include child DisplayObjects.
|
|
|
|
/// To get the bounds including children, use `bounds`, `local_bounds`, or `world_bounds`.
|
|
|
|
///
|
|
|
|
/// Implementors must override this method.
|
|
|
|
/// Leaf DisplayObjects should return their bounds.
|
|
|
|
/// Composite DisplayObjects that only contain children should return `&Default::default()`
|
|
|
|
fn self_bounds(&self) -> BoundingBox;
|
|
|
|
|
|
|
|
/// The untransformed bounding box of this object including children.
|
|
|
|
fn bounds(&self) -> BoundingBox {
|
|
|
|
self.bounds_with_transform(&Matrix::default())
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
|
|
|
|
2020-02-21 20:57:45 +00:00
|
|
|
/// The local bounding box of this object including children, in its parent's coordinate system.
|
2019-08-14 18:59:07 +00:00
|
|
|
fn local_bounds(&self) -> BoundingBox {
|
2020-02-21 20:57:45 +00:00
|
|
|
self.bounds_with_transform(&self.matrix())
|
2019-08-14 18:59:07 +00:00
|
|
|
}
|
|
|
|
|
2020-02-21 20:57:45 +00:00
|
|
|
/// The world bounding box of this object including children, relative to the stage.
|
2019-08-14 18:59:07 +00:00
|
|
|
fn world_bounds(&self) -> BoundingBox {
|
2020-02-21 20:57:45 +00:00
|
|
|
self.bounds_with_transform(&self.local_to_global_matrix())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the bounds of this object and all children, transformed by a given matrix.
|
|
|
|
/// This function recurses down and transforms the AABB each child before adding
|
|
|
|
/// it to the bounding box. This gives a tighter AABB then if we simply transformed
|
|
|
|
/// the overall AABB.
|
|
|
|
fn bounds_with_transform(&self, matrix: &Matrix) -> BoundingBox {
|
|
|
|
let mut bounds = self.self_bounds().transform(matrix);
|
2020-11-07 22:41:52 +00:00
|
|
|
|
|
|
|
if let Some(ctr) = self.as_container() {
|
|
|
|
for child in ctr.iter_execution_list() {
|
|
|
|
let matrix = *matrix * *child.matrix();
|
|
|
|
bounds.union(&child.bounds_with_transform(&matrix));
|
|
|
|
}
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2020-11-07 22:41:52 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
bounds
|
2019-08-14 18:59:07 +00:00
|
|
|
}
|
2020-02-21 20:57:45 +00:00
|
|
|
|
2019-09-16 16:13:12 +00:00
|
|
|
fn place_frame(&self) -> u16;
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_place_frame(&self, gc_context: MutationContext<'gc, '_>, frame: u16);
|
2019-08-14 18:59:07 +00:00
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn transform(&self) -> Ref<Transform>;
|
|
|
|
fn matrix(&self) -> Ref<Matrix>;
|
2021-03-06 16:10:25 +00:00
|
|
|
fn matrix_mut(&self, gc_context: MutationContext<'gc, '_>) -> RefMut<Matrix>;
|
|
|
|
fn set_matrix(&self, gc_context: MutationContext<'gc, '_>, matrix: &Matrix);
|
2019-12-07 02:29:36 +00:00
|
|
|
fn color_transform(&self) -> Ref<ColorTransform>;
|
2021-03-06 16:10:25 +00:00
|
|
|
fn color_transform_mut(&self, gc_context: MutationContext<'gc, '_>) -> RefMut<ColorTransform>;
|
2019-12-07 02:29:36 +00:00
|
|
|
fn set_color_transform(
|
2020-08-22 05:59:45 +00:00
|
|
|
&self,
|
2021-03-06 16:10:25 +00:00
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2019-12-07 02:29:36 +00:00
|
|
|
color_transform: &ColorTransform,
|
|
|
|
);
|
2019-12-04 01:52:00 +00:00
|
|
|
|
2020-02-21 20:57:45 +00:00
|
|
|
/// Returns the matrix for transforming from this object's local space to global stage space.
|
|
|
|
fn local_to_global_matrix(&self) -> Matrix {
|
2019-12-19 19:51:56 +00:00
|
|
|
let mut node = self.parent();
|
|
|
|
let mut matrix = *self.matrix();
|
|
|
|
while let Some(display_object) = node {
|
2019-12-21 23:35:15 +00:00
|
|
|
matrix = *display_object.matrix() * matrix;
|
2019-12-19 19:51:56 +00:00
|
|
|
node = display_object.parent();
|
|
|
|
}
|
2020-02-21 20:57:45 +00:00
|
|
|
matrix
|
2019-12-19 19:51:56 +00:00
|
|
|
}
|
|
|
|
|
2020-02-21 20:57:45 +00:00
|
|
|
/// Returns the matrix for transforming from global stage to this object's local space.
|
|
|
|
fn global_to_local_matrix(&self) -> Matrix {
|
2021-03-06 21:41:56 +00:00
|
|
|
let mut matrix = self.local_to_global_matrix();
|
2019-12-16 09:51:19 +00:00
|
|
|
matrix.invert();
|
2020-02-21 20:57:45 +00:00
|
|
|
matrix
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts a local position to a global stage position
|
|
|
|
fn local_to_global(&self, local: (Twips, Twips)) -> (Twips, Twips) {
|
|
|
|
self.local_to_global_matrix() * local
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts a local position on the stage to a local position on this display object
|
|
|
|
fn global_to_local(&self, global: (Twips, Twips)) -> (Twips, Twips) {
|
|
|
|
self.global_to_local_matrix() * global
|
2019-12-16 09:51:19 +00:00
|
|
|
}
|
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
/// The `x` position in pixels of this display object in local space.
|
|
|
|
/// Returned by the `_x`/`x` ActionScript properties.
|
|
|
|
fn x(&self) -> f64;
|
|
|
|
|
|
|
|
/// Sets the `x` position in pixels of this display object in local space.
|
|
|
|
/// Set by the `_x`/`x` ActionScript properties.
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_x(&self, gc_context: MutationContext<'gc, '_>, value: f64);
|
2019-12-04 01:52:00 +00:00
|
|
|
|
|
|
|
/// The `y` position in pixels of this display object in local space.
|
|
|
|
/// Returned by the `_y`/`y` ActionScript properties.
|
|
|
|
fn y(&self) -> f64;
|
|
|
|
|
|
|
|
/// Sets the `y` position in pixels of this display object in local space.
|
|
|
|
/// Set by the `_y`/`y` ActionScript properties.
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_y(&self, gc_context: MutationContext<'gc, '_>, value: f64);
|
2019-12-04 01:52:00 +00:00
|
|
|
|
2020-09-25 23:18:31 +00:00
|
|
|
/// The rotation in degrees this display object in local space.
|
2019-12-04 01:52:00 +00:00
|
|
|
/// Returned by the `_rotation`/`rotation` ActionScript properties.
|
2020-09-25 23:18:31 +00:00
|
|
|
fn rotation(&self, gc_context: MutationContext<'gc, '_>) -> Degrees;
|
2019-12-04 01:52:00 +00:00
|
|
|
|
2020-09-25 23:18:31 +00:00
|
|
|
/// Sets the rotation in degrees this display object in local space.
|
2019-12-04 01:52:00 +00:00
|
|
|
/// Set by the `_rotation`/`rotation` ActionScript properties.
|
2020-09-25 23:18:31 +00:00
|
|
|
fn set_rotation(&self, gc_context: MutationContext<'gc, '_>, radians: Degrees);
|
2019-12-04 01:52:00 +00:00
|
|
|
|
|
|
|
/// The X axis scale for this display object in local space.
|
2020-09-25 23:18:31 +00:00
|
|
|
/// The normal scale is 100.
|
2019-12-04 01:52:00 +00:00
|
|
|
/// Returned by the `_xscale`/`scaleX` ActionScript properties.
|
2020-09-25 03:17:07 +00:00
|
|
|
fn scale_x(&self, gc_context: MutationContext<'gc, '_>) -> Percent;
|
2019-12-04 01:52:00 +00:00
|
|
|
|
|
|
|
/// Sets the scale of the X axis for this display object in local space.
|
2020-09-25 23:18:31 +00:00
|
|
|
/// The normal scale is 100.
|
2019-12-04 01:52:00 +00:00
|
|
|
/// Set by the `_xscale`/`scaleX` ActionScript properties.
|
2020-09-25 03:17:07 +00:00
|
|
|
fn set_scale_x(&self, gc_context: MutationContext<'gc, '_>, value: Percent);
|
2019-12-04 01:52:00 +00:00
|
|
|
|
|
|
|
/// The Y axis scale for this display object in local space.
|
|
|
|
/// The normal scale is 1.
|
|
|
|
/// Returned by the `_yscale`/`scaleY` ActionScript properties.
|
2020-09-25 03:17:07 +00:00
|
|
|
fn scale_y(&self, gc_context: MutationContext<'gc, '_>) -> Percent;
|
2019-12-04 01:52:00 +00:00
|
|
|
|
|
|
|
/// Sets the Y axis scale for this display object in local space.
|
|
|
|
/// The normal scale is 1.
|
|
|
|
/// Returned by the `_yscale`/`scaleY` ActionScript properties.
|
2020-09-25 03:17:07 +00:00
|
|
|
fn set_scale_y(&self, gc_context: MutationContext<'gc, '_>, value: Percent);
|
2019-12-04 01:52:00 +00:00
|
|
|
|
|
|
|
/// Sets the pixel width of this display object in local space.
|
|
|
|
/// The width is based on the AABB of the object.
|
|
|
|
/// Returned by the ActionScript `_width`/`width` properties.
|
|
|
|
fn width(&self) -> f64 {
|
|
|
|
let bounds = self.local_bounds();
|
2020-11-27 06:19:55 +00:00
|
|
|
(bounds.x_max.saturating_sub(bounds.x_min)).to_pixels()
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the pixel width of this display object in local space.
|
|
|
|
/// The width is based on the AABB of the object.
|
|
|
|
/// Set by the ActionScript `_width`/`width` properties.
|
|
|
|
/// This does odd things on rotated clips to match the behavior of Flash.
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_width(&self, gc_context: MutationContext<'gc, '_>, value: f64) {
|
2020-02-21 20:57:45 +00:00
|
|
|
let object_bounds = self.bounds();
|
2019-12-04 01:52:00 +00:00
|
|
|
let object_width = (object_bounds.x_max - object_bounds.x_min).to_pixels();
|
|
|
|
let object_height = (object_bounds.y_max - object_bounds.y_min).to_pixels();
|
|
|
|
let aspect_ratio = object_height / object_width;
|
|
|
|
|
|
|
|
let (target_scale_x, target_scale_y) = if object_width != 0.0 {
|
|
|
|
(value / object_width, value / object_height)
|
|
|
|
} else {
|
|
|
|
(0.0, 0.0)
|
|
|
|
};
|
|
|
|
|
|
|
|
// No idea about the derivation of this -- figured it out via lots of trial and error.
|
|
|
|
// It has to do with the length of the sides A, B of an AABB enclosing the object's OBB with sides a, b:
|
|
|
|
// A = sin(t) * a + cos(t) * b
|
|
|
|
// B = cos(t) * a + sin(t) * b
|
2020-09-25 03:17:07 +00:00
|
|
|
let prev_scale_x = self.scale_x(gc_context).into_unit();
|
|
|
|
let prev_scale_y = self.scale_y(gc_context).into_unit();
|
2019-12-04 01:52:00 +00:00
|
|
|
let rotation = self.rotation(gc_context);
|
2020-09-25 23:18:31 +00:00
|
|
|
let cos = f64::abs(f64::cos(rotation.into_radians()));
|
|
|
|
let sin = f64::abs(f64::sin(rotation.into_radians()));
|
2021-04-06 21:58:13 +00:00
|
|
|
let mut new_scale_x = aspect_ratio * (cos * target_scale_x + sin * target_scale_y)
|
2019-12-04 01:52:00 +00:00
|
|
|
/ ((cos + aspect_ratio * sin) * (aspect_ratio * cos + sin));
|
2021-04-06 21:58:13 +00:00
|
|
|
let mut new_scale_y =
|
2019-12-04 01:52:00 +00:00
|
|
|
(sin * prev_scale_x + aspect_ratio * cos * prev_scale_y) / (aspect_ratio * cos + sin);
|
2021-04-06 21:58:13 +00:00
|
|
|
|
|
|
|
if !new_scale_x.is_finite() {
|
|
|
|
new_scale_x = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !new_scale_y.is_finite() {
|
|
|
|
new_scale_y = 0.0;
|
|
|
|
}
|
|
|
|
|
2020-09-25 03:17:07 +00:00
|
|
|
self.set_scale_x(gc_context, Percent::from_unit(new_scale_x));
|
|
|
|
self.set_scale_y(gc_context, Percent::from_unit(new_scale_y));
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-03-06 16:10:25 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
/// Gets the pixel height of the AABB containing this display object in local space.
|
|
|
|
/// Returned by the ActionScript `_height`/`height` properties.
|
|
|
|
fn height(&self) -> f64 {
|
|
|
|
let bounds = self.local_bounds();
|
2020-11-27 06:19:55 +00:00
|
|
|
(bounds.y_max.saturating_sub(bounds.y_min)).to_pixels()
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-03-06 16:10:25 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
/// Sets the pixel height of this display object in local space.
|
|
|
|
/// Set by the ActionScript `_height`/`height` properties.
|
|
|
|
/// This does odd things on rotated clips to match the behavior of Flash.
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_height(&self, gc_context: MutationContext<'gc, '_>, value: f64) {
|
2020-02-21 20:57:45 +00:00
|
|
|
let object_bounds = self.bounds();
|
2019-12-04 01:52:00 +00:00
|
|
|
let object_width = (object_bounds.x_max - object_bounds.x_min).to_pixels();
|
|
|
|
let object_height = (object_bounds.y_max - object_bounds.y_min).to_pixels();
|
|
|
|
let aspect_ratio = object_width / object_height;
|
|
|
|
|
|
|
|
let (target_scale_x, target_scale_y) = if object_height != 0.0 {
|
|
|
|
(value / object_width, value / object_height)
|
|
|
|
} else {
|
|
|
|
(0.0, 0.0)
|
|
|
|
};
|
|
|
|
|
|
|
|
// No idea about the derivation of this -- figured it out via lots of trial and error.
|
|
|
|
// It has to do with the length of the sides A, B of an AABB enclosing the object's OBB with sides a, b:
|
|
|
|
// A = sin(t) * a + cos(t) * b
|
|
|
|
// B = cos(t) * a + sin(t) * b
|
2020-09-25 03:17:07 +00:00
|
|
|
let prev_scale_x = self.scale_x(gc_context).into_unit();
|
|
|
|
let prev_scale_y = self.scale_y(gc_context).into_unit();
|
2019-12-04 01:52:00 +00:00
|
|
|
let rotation = self.rotation(gc_context);
|
2020-09-25 23:18:31 +00:00
|
|
|
let cos = f64::abs(f64::cos(rotation.into_radians()));
|
|
|
|
let sin = f64::abs(f64::sin(rotation.into_radians()));
|
2021-04-06 21:58:13 +00:00
|
|
|
let mut new_scale_x =
|
2019-12-04 01:52:00 +00:00
|
|
|
(aspect_ratio * cos * prev_scale_x + sin * prev_scale_y) / (aspect_ratio * cos + sin);
|
2021-04-06 21:58:13 +00:00
|
|
|
let mut new_scale_y = aspect_ratio * (sin * target_scale_x + cos * target_scale_y)
|
2019-12-04 01:52:00 +00:00
|
|
|
/ ((cos + aspect_ratio * sin) * (aspect_ratio * cos + sin));
|
2021-04-06 21:58:13 +00:00
|
|
|
|
|
|
|
if !new_scale_x.is_finite() {
|
|
|
|
new_scale_x = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !new_scale_y.is_finite() {
|
|
|
|
new_scale_y = 0.0;
|
|
|
|
}
|
|
|
|
|
2020-09-25 03:17:07 +00:00
|
|
|
self.set_scale_x(gc_context, Percent::from_unit(new_scale_x));
|
|
|
|
self.set_scale_y(gc_context, Percent::from_unit(new_scale_y));
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-03-06 16:10:25 +00:00
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
/// The opacity of this display object.
|
|
|
|
/// 1 is fully opaque.
|
|
|
|
/// Returned by the `_alpha`/`alpha` ActionScript properties.
|
|
|
|
fn alpha(&self) -> f64;
|
|
|
|
|
|
|
|
/// Sets the opacity of this display object.
|
|
|
|
/// 1 is fully opaque.
|
|
|
|
/// Set by the `_alpha`/`alpha` ActionScript properties.
|
|
|
|
fn set_alpha(&self, gc_context: MutationContext<'gc, '_>, value: f64);
|
2021-03-06 16:10:25 +00:00
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn name(&self) -> Ref<str>;
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_name(&self, gc_context: MutationContext<'gc, '_>, name: &str);
|
2019-12-15 07:12:32 +00:00
|
|
|
|
2019-12-03 08:19:35 +00:00
|
|
|
/// Returns the dot-syntax path to this display object, e.g. `_level0.foo.clip`
|
|
|
|
fn path(&self) -> String {
|
2021-05-05 02:14:19 +00:00
|
|
|
if let Some(parent) = self.avm1_parent() {
|
2019-12-03 08:19:35 +00:00
|
|
|
let mut path = parent.path();
|
2020-09-07 19:18:25 +00:00
|
|
|
path.push('.');
|
2019-12-03 08:19:35 +00:00
|
|
|
path.push_str(&*self.name());
|
|
|
|
path
|
|
|
|
} else {
|
2020-02-01 23:29:21 +00:00
|
|
|
format!("_level{}", self.depth())
|
2019-12-03 08:19:35 +00:00
|
|
|
}
|
|
|
|
}
|
2019-12-15 07:12:32 +00:00
|
|
|
|
|
|
|
/// Returns the Flash 4 slash-syntax path to this display object, e.g. `/foo/clip`.
|
|
|
|
/// Returned by the `_target` property in AVM1.
|
|
|
|
fn slash_path(&self) -> String {
|
2020-09-04 07:41:08 +00:00
|
|
|
fn build_slash_path(object: DisplayObject<'_>) -> String {
|
2021-05-05 02:14:19 +00:00
|
|
|
if let Some(parent) = object.avm1_parent() {
|
2020-09-04 07:41:08 +00:00
|
|
|
let mut path = build_slash_path(parent);
|
|
|
|
path.push('/');
|
|
|
|
path.push_str(&*object.name());
|
|
|
|
path
|
|
|
|
} else {
|
|
|
|
let level = object.depth();
|
|
|
|
if level == 0 {
|
|
|
|
// _level0 does not append its name in slash syntax.
|
|
|
|
String::new()
|
|
|
|
} else {
|
|
|
|
// Other levels do append their name.
|
|
|
|
format!("_level{}", level)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 02:14:19 +00:00
|
|
|
if self.avm1_parent().is_some() {
|
2020-09-04 07:41:08 +00:00
|
|
|
build_slash_path((*self).into())
|
2019-12-15 07:12:32 +00:00
|
|
|
} else {
|
2020-09-04 07:41:08 +00:00
|
|
|
// _target of _level0 should just be '/'.
|
|
|
|
'/'.to_string()
|
2019-12-15 07:12:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-12 17:48:00 +00:00
|
|
|
fn clip_depth(&self) -> Depth;
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_clip_depth(&self, gc_context: MutationContext<'gc, '_>, depth: Depth);
|
2021-04-15 03:29:12 +00:00
|
|
|
|
|
|
|
/// Retrieve the parent of this display object.
|
|
|
|
///
|
2021-05-05 02:14:19 +00:00
|
|
|
/// This version of the function merely exposes the display object parent,
|
|
|
|
/// without any further filtering.
|
|
|
|
fn parent(&self) -> Option<DisplayObject<'gc>>;
|
2021-04-15 03:29:12 +00:00
|
|
|
|
|
|
|
/// Set the parent of this display object.
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_parent(&self, gc_context: MutationContext<'gc, '_>, parent: Option<DisplayObject<'gc>>);
|
2021-04-15 03:29:12 +00:00
|
|
|
|
|
|
|
/// Retrieve the parent of this display object.
|
|
|
|
///
|
2021-05-05 02:14:19 +00:00
|
|
|
/// This version of the function implements the concept of parenthood as
|
|
|
|
/// seen in AVM1. Notably, it disallows access to the `Stage`; for an
|
|
|
|
/// unfiltered concept of parent, use the `parent` method.
|
|
|
|
fn avm1_parent(&self) -> Option<DisplayObject<'gc>> {
|
|
|
|
self.parent().filter(|p| p.as_stage().is_none())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieve the parent of this display object.
|
|
|
|
///
|
|
|
|
/// This version of the function implements the concept of parenthood as
|
|
|
|
/// seen in AVM2. Notably, it disallows access to non-container parents.
|
|
|
|
fn avm2_parent(&self) -> Option<DisplayObject<'gc>> {
|
2021-05-05 02:22:03 +00:00
|
|
|
self.parent().filter(|p| p.as_container().is_some())
|
2021-04-15 03:29:12 +00:00
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn prev_sibling(&self) -> Option<DisplayObject<'gc>>;
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_prev_sibling(
|
|
|
|
&self,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
node: Option<DisplayObject<'gc>>,
|
|
|
|
);
|
2019-12-07 02:29:36 +00:00
|
|
|
fn next_sibling(&self) -> Option<DisplayObject<'gc>>;
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_next_sibling(
|
|
|
|
&self,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
node: Option<DisplayObject<'gc>>,
|
|
|
|
);
|
2020-11-04 07:30:48 +00:00
|
|
|
fn masker(&self) -> Option<DisplayObject<'gc>>;
|
|
|
|
fn set_masker(
|
|
|
|
&self,
|
2021-03-06 16:10:25 +00:00
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2020-11-04 07:30:48 +00:00
|
|
|
node: Option<DisplayObject<'gc>>,
|
|
|
|
remove_old_link: bool,
|
|
|
|
);
|
|
|
|
fn maskee(&self) -> Option<DisplayObject<'gc>>;
|
|
|
|
fn set_maskee(
|
|
|
|
&self,
|
2021-03-06 16:10:25 +00:00
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2020-11-04 07:30:48 +00:00
|
|
|
node: Option<DisplayObject<'gc>>,
|
|
|
|
remove_old_link: bool,
|
|
|
|
);
|
2020-03-27 22:16:01 +00:00
|
|
|
|
2019-10-18 04:10:13 +00:00
|
|
|
fn removed(&self) -> bool;
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_removed(&self, gc_context: MutationContext<'gc, '_>, value: bool);
|
2019-12-15 17:36:58 +00:00
|
|
|
|
|
|
|
/// Whether this display object is visible.
|
|
|
|
/// Invisible objects are not rendered, but otherwise continue to exist normally.
|
|
|
|
/// Returned by the `_visible`/`visible` ActionScript properties.
|
2019-12-04 01:52:00 +00:00
|
|
|
fn visible(&self) -> bool;
|
2019-12-15 17:36:58 +00:00
|
|
|
|
|
|
|
/// Sets whether this display object will be visible.
|
|
|
|
/// Invisible objects are not rendered, but otherwise continue to exist normally.
|
|
|
|
/// Returned by the `_visible`/`visible` ActionScript properties.
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_visible(&self, gc_context: MutationContext<'gc, '_>, value: bool);
|
2019-12-15 17:32:42 +00:00
|
|
|
|
2021-01-24 08:16:07 +00:00
|
|
|
/// The sound transform for sounds played inside this display object.
|
|
|
|
fn sound_transform(&self) -> Ref<SoundTransform>;
|
|
|
|
|
|
|
|
/// The sound transform for sounds played inside this display object.
|
|
|
|
fn set_sound_transform(
|
|
|
|
&self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
sound_transform: SoundTransform,
|
|
|
|
);
|
|
|
|
|
2020-12-01 02:50:50 +00:00
|
|
|
/// Whether this display object is used as the _root of itself and its children.
|
|
|
|
/// Returned by the `_lockroot` ActionScript property.
|
|
|
|
fn lock_root(&self) -> bool;
|
|
|
|
|
|
|
|
/// Sets whether this display object is used as the _root of itself and its children.
|
|
|
|
/// Returned by the `_lockroot` ActionScript property.
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_lock_root(&self, gc_context: MutationContext<'gc, '_>, value: bool);
|
2020-12-01 02:50:50 +00:00
|
|
|
|
2019-12-15 17:32:42 +00:00
|
|
|
/// Whether this display object has been transformed by ActionScript.
|
|
|
|
/// When this flag is set, changes from SWF `PlaceObject` tags are ignored.
|
|
|
|
fn transformed_by_script(&self) -> bool;
|
|
|
|
|
|
|
|
/// Sets whether this display object has been transformed by ActionScript.
|
|
|
|
/// When this flag is set, changes from SWF `PlaceObject` tags are ignored.
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_transformed_by_script(&self, gc_context: MutationContext<'gc, '_>, value: bool);
|
2019-12-15 17:32:42 +00:00
|
|
|
|
2020-10-29 23:09:57 +00:00
|
|
|
/// Called whenever the focus tracker has deemed this display object worthy, or no longer worthy,
|
|
|
|
/// of being the currently focused object.
|
|
|
|
/// This should only be called by the focus manager. To change a focus, go through that.
|
2021-03-06 16:10:25 +00:00
|
|
|
fn on_focus_changed(&self, _gc_context: MutationContext<'gc, '_>, _focused: bool) {}
|
2020-10-29 23:09:57 +00:00
|
|
|
|
2020-10-30 15:14:05 +00:00
|
|
|
/// Whether or not this clip may be focusable for keyboard input.
|
|
|
|
fn is_focusable(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2020-10-24 02:19:30 +00:00
|
|
|
/// Whether this display object has been created by ActionScript 3.
|
|
|
|
/// When this flag is set, changes from SWF `RemoveObject` tags are
|
|
|
|
/// ignored.
|
|
|
|
fn placed_by_script(&self) -> bool;
|
|
|
|
|
|
|
|
/// Sets whether this display object has been created by ActionScript 3.
|
|
|
|
/// When this flag is set, changes from SWF `RemoveObject` tags are
|
|
|
|
/// ignored.
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_placed_by_script(&self, gc_context: MutationContext<'gc, '_>, value: bool);
|
2020-10-24 02:19:30 +00:00
|
|
|
|
2020-11-28 19:15:30 +00:00
|
|
|
/// Whether this display object has been instantiated by the timeline.
|
|
|
|
/// When this flag is set, attempts to change the object's name from AVM2
|
|
|
|
/// throw an exception.
|
|
|
|
fn instantiated_by_timeline(&self) -> bool;
|
|
|
|
|
|
|
|
/// Sets whether this display object has been instantiated by the timeline.
|
|
|
|
/// When this flag is set, attempts to change the object's name from AVM2
|
|
|
|
/// throw an exception.
|
2021-03-06 16:10:25 +00:00
|
|
|
fn set_instantiated_by_timeline(&self, gc_context: MutationContext<'gc, '_>, value: bool);
|
2020-11-28 19:15:30 +00:00
|
|
|
|
2019-12-24 10:41:35 +00:00
|
|
|
/// Executes and propagates the given clip event.
|
|
|
|
/// Events execute inside-out; the deepest child will react first, followed by its parent, and
|
|
|
|
/// so forth.
|
2020-05-23 23:51:40 +00:00
|
|
|
fn handle_clip_event(
|
2020-11-25 00:35:15 +00:00
|
|
|
&self,
|
2020-05-23 23:51:40 +00:00
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
_event: ClipEvent,
|
|
|
|
) -> ClipEventResult {
|
|
|
|
ClipEventResult::NotHandled
|
2019-12-16 10:31:54 +00:00
|
|
|
}
|
|
|
|
|
2021-01-19 02:50:21 +00:00
|
|
|
/// Emit an `enterFrame` event on this DisplayObject and any children it
|
|
|
|
/// may have.
|
|
|
|
fn enter_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
2021-01-22 01:16:23 +00:00
|
|
|
let mut enter_frame_evt = Avm2Event::new("enterFrame");
|
|
|
|
enter_frame_evt.set_bubbles(false);
|
|
|
|
enter_frame_evt.set_cancelable(false);
|
2021-01-19 02:50:21 +00:00
|
|
|
|
2021-01-22 01:16:23 +00:00
|
|
|
let dobject_proto = context.avm2.prototypes().display_object;
|
2021-01-19 02:50:21 +00:00
|
|
|
|
2021-01-22 01:16:23 +00:00
|
|
|
if let Err(e) = Avm2::broadcast_event(context, enter_frame_evt, dobject_proto) {
|
|
|
|
log::error!(
|
|
|
|
"Encountered AVM2 error when broadcasting enterFrame event: {}",
|
|
|
|
e
|
|
|
|
);
|
2021-01-19 02:50:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-03 03:19:24 +00:00
|
|
|
/// Construct all display objects that the timeline indicates should exist
|
|
|
|
/// this frame, and their children.
|
|
|
|
///
|
|
|
|
/// This function should ensure the following, from the point of view of
|
|
|
|
/// downstream VMs:
|
|
|
|
///
|
|
|
|
/// 1. That the object itself has been allocated, if not constructed
|
|
|
|
/// 2. That newly created children have been instantiated and are present
|
|
|
|
/// as properties on the class
|
|
|
|
fn construct_frame(&self, _context: &mut UpdateContext<'_, 'gc, '_>) {}
|
|
|
|
|
|
|
|
/// Execute all other timeline actions on this object and it's children.
|
2020-11-25 00:35:15 +00:00
|
|
|
fn run_frame(&self, _context: &mut UpdateContext<'_, 'gc, '_>) {}
|
2021-01-19 02:50:21 +00:00
|
|
|
|
|
|
|
/// Emit an `frameConstructed` event on this DisplayObject and any children it
|
|
|
|
/// may have.
|
|
|
|
fn frame_constructed(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
2021-01-22 01:16:23 +00:00
|
|
|
let mut frame_constructed_evt = Avm2Event::new("frameConstructed");
|
|
|
|
frame_constructed_evt.set_bubbles(false);
|
|
|
|
frame_constructed_evt.set_cancelable(false);
|
2021-01-19 02:50:21 +00:00
|
|
|
|
2021-01-22 01:16:23 +00:00
|
|
|
let dobject_proto = context.avm2.prototypes().display_object;
|
2021-01-19 02:50:21 +00:00
|
|
|
|
2021-01-22 01:16:23 +00:00
|
|
|
if let Err(e) = Avm2::broadcast_event(context, frame_constructed_evt, dobject_proto) {
|
|
|
|
log::error!(
|
|
|
|
"Encountered AVM2 error when broadcasting frameConstructed event: {}",
|
|
|
|
e
|
|
|
|
);
|
2021-01-19 02:50:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Run any frame scripts (if they exist and this object needs to run them).
|
|
|
|
fn run_frame_scripts(self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
|
|
|
if let Some(container) = self.as_container() {
|
|
|
|
for child in container.iter_execution_list() {
|
|
|
|
child.run_frame_scripts(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-22 01:16:23 +00:00
|
|
|
/// Emit an `exitFrame` broadcast event.
|
2021-01-19 02:50:21 +00:00
|
|
|
fn exit_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
2021-01-22 01:16:23 +00:00
|
|
|
let mut exit_frame_evt = Avm2Event::new("exitFrame");
|
|
|
|
exit_frame_evt.set_bubbles(false);
|
|
|
|
exit_frame_evt.set_cancelable(false);
|
2021-01-19 02:50:21 +00:00
|
|
|
|
2021-01-22 01:16:23 +00:00
|
|
|
let dobject_proto = context.avm2.prototypes().display_object;
|
2021-01-19 02:50:21 +00:00
|
|
|
|
2021-01-22 01:16:23 +00:00
|
|
|
if let Err(e) = Avm2::broadcast_event(context, exit_frame_evt, dobject_proto) {
|
|
|
|
log::error!(
|
|
|
|
"Encountered AVM2 error when broadcasting exitFrame event: {}",
|
|
|
|
e
|
|
|
|
);
|
2021-01-19 02:50:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-04 07:30:48 +00:00
|
|
|
fn render_self(&self, _context: &mut RenderContext<'_, 'gc>) {}
|
|
|
|
|
|
|
|
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
|
2021-04-16 21:55:57 +00:00
|
|
|
render_base((*self).into(), context)
|
2020-11-04 07:30:48 +00:00
|
|
|
}
|
2020-06-23 04:07:27 +00:00
|
|
|
|
2020-08-22 05:59:45 +00:00
|
|
|
fn unload(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
2020-06-23 04:07:27 +00:00
|
|
|
// Unload children.
|
2020-11-07 22:41:52 +00:00
|
|
|
if let Some(ctr) = self.as_container() {
|
|
|
|
for child in ctr.iter_execution_list() {
|
|
|
|
child.unload(context);
|
|
|
|
}
|
2020-06-11 23:50:03 +00:00
|
|
|
}
|
2020-06-23 04:07:27 +00:00
|
|
|
|
2021-02-28 11:26:59 +00:00
|
|
|
if let Some(node) = self.maskee() {
|
|
|
|
node.set_masker(context.gc_context, None, true);
|
|
|
|
} else if let Some(node) = self.masker() {
|
|
|
|
node.set_maskee(context.gc_context, None, true);
|
|
|
|
}
|
|
|
|
|
2020-06-23 04:07:27 +00:00
|
|
|
// Unregister any text field variable bindings, and replace them on the unbound list.
|
2020-07-21 04:24:02 +00:00
|
|
|
if let Avm1Value::Object(object) = self.object() {
|
2020-06-23 04:07:27 +00:00
|
|
|
if let Some(stage_object) = object.as_stage_object() {
|
|
|
|
stage_object.unregister_text_field_bindings(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-01 20:05:19 +00:00
|
|
|
self.set_removed(context.gc_context, true);
|
|
|
|
}
|
2020-06-23 04:07:27 +00:00
|
|
|
|
2021-04-15 03:29:12 +00:00
|
|
|
fn as_stage(&self) -> Option<Stage<'gc>> {
|
|
|
|
None
|
|
|
|
}
|
2021-04-23 01:10:29 +00:00
|
|
|
fn as_avm1_button(&self) -> Option<Avm1Button<'gc>> {
|
2019-08-19 14:53:12 +00:00
|
|
|
None
|
|
|
|
}
|
2021-04-24 01:16:27 +00:00
|
|
|
fn as_avm2_button(&self) -> Option<Avm2Button<'gc>> {
|
|
|
|
None
|
|
|
|
}
|
2019-12-09 22:19:35 +00:00
|
|
|
fn as_movie_clip(&self) -> Option<MovieClip<'gc>> {
|
2019-08-19 14:53:12 +00:00
|
|
|
None
|
|
|
|
}
|
2019-12-19 09:31:08 +00:00
|
|
|
fn as_edit_text(&self) -> Option<EditText<'gc>> {
|
|
|
|
None
|
|
|
|
}
|
2019-12-09 22:19:35 +00:00
|
|
|
fn as_morph_shape(&self) -> Option<MorphShape<'gc>> {
|
2019-05-12 16:55:48 +00:00
|
|
|
None
|
|
|
|
}
|
2020-11-07 22:41:52 +00:00
|
|
|
fn as_container(self) -> Option<DisplayObjectContainer<'gc>> {
|
|
|
|
None
|
|
|
|
}
|
2020-12-22 04:28:16 +00:00
|
|
|
fn as_video(self) -> Option<Video<'gc>> {
|
|
|
|
None
|
|
|
|
}
|
2021-02-27 02:09:27 +00:00
|
|
|
fn as_drawing(&self, _gc_context: MutationContext<'gc, '_>) -> Option<RefMut<'_, Drawing>> {
|
|
|
|
None
|
|
|
|
}
|
2020-11-07 22:41:52 +00:00
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn apply_place_object(
|
2020-08-22 05:59:45 +00:00
|
|
|
&self,
|
2020-12-22 04:28:16 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2021-01-28 10:49:24 +00:00
|
|
|
placing_movie: Option<Arc<SwfMovie>>,
|
2019-12-07 02:29:36 +00:00
|
|
|
place_object: &swf::PlaceObject,
|
|
|
|
) {
|
2019-12-15 17:32:42 +00:00
|
|
|
// PlaceObject tags only apply if this onject has not been dynamically moved by AS code.
|
|
|
|
if !self.transformed_by_script() {
|
|
|
|
if let Some(matrix) = &place_object.matrix {
|
2020-12-22 04:28:16 +00:00
|
|
|
self.set_matrix(context.gc_context, &matrix);
|
2019-12-15 17:32:42 +00:00
|
|
|
}
|
|
|
|
if let Some(color_transform) = &place_object.color_transform {
|
2020-12-22 04:28:16 +00:00
|
|
|
self.set_color_transform(context.gc_context, &color_transform.clone().into());
|
2019-12-15 17:32:42 +00:00
|
|
|
}
|
|
|
|
if let Some(name) = &place_object.name {
|
2021-01-20 20:46:22 +00:00
|
|
|
let encoding = swf::SwfStr::encoding_for_version(self.swf_version());
|
|
|
|
let name = name.to_str_lossy(encoding);
|
2020-12-22 04:28:16 +00:00
|
|
|
self.set_name(context.gc_context, &name);
|
2019-09-16 16:13:12 +00:00
|
|
|
}
|
2019-12-15 17:32:42 +00:00
|
|
|
if let Some(clip_depth) = place_object.clip_depth {
|
2020-12-22 04:28:16 +00:00
|
|
|
self.set_clip_depth(context.gc_context, clip_depth.into());
|
2019-12-15 17:32:42 +00:00
|
|
|
}
|
|
|
|
if let Some(ratio) = place_object.ratio {
|
|
|
|
if let Some(mut morph_shape) = self.as_morph_shape() {
|
2020-12-22 04:28:16 +00:00
|
|
|
morph_shape.set_ratio(context.gc_context, ratio);
|
2020-12-23 02:20:28 +00:00
|
|
|
} else if let Some(video) = self.as_video() {
|
|
|
|
video.seek(context, ratio.into());
|
2019-12-15 17:32:42 +00:00
|
|
|
}
|
|
|
|
}
|
2019-12-01 19:24:47 +00:00
|
|
|
// Clip events only apply to movie clips.
|
2020-09-04 00:41:58 +00:00
|
|
|
if let (Some(clip_actions), Some(clip)) =
|
|
|
|
(&place_object.clip_actions, self.as_movie_clip())
|
|
|
|
{
|
2019-12-02 00:07:13 +00:00
|
|
|
// Convert from `swf::ClipAction` to Ruffle's `ClipAction`.
|
|
|
|
use crate::display_object::movie_clip::ClipAction;
|
2021-01-28 10:49:24 +00:00
|
|
|
if let Some(placing_movie) = placing_movie {
|
|
|
|
clip.set_clip_actions(
|
2020-12-22 04:28:16 +00:00
|
|
|
context.gc_context,
|
2021-01-28 10:49:24 +00:00
|
|
|
clip_actions
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.map(|a| {
|
|
|
|
ClipAction::from_action_and_movie(a, Arc::clone(&placing_movie))
|
|
|
|
})
|
|
|
|
.flatten()
|
|
|
|
.collect(),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
// This probably shouldn't happen; we should always have a movie.
|
|
|
|
log::error!("No movie when trying to set clip event");
|
|
|
|
}
|
2019-12-01 19:24:47 +00:00
|
|
|
}
|
2019-12-15 17:32:42 +00:00
|
|
|
// TODO: Others will go here eventually.
|
2019-09-16 16:13:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn copy_display_properties_from(
|
2020-08-22 05:59:45 +00:00
|
|
|
&self,
|
2019-12-07 02:29:36 +00:00
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
other: DisplayObject<'gc>,
|
|
|
|
) {
|
|
|
|
self.set_matrix(gc_context, &*other.matrix());
|
|
|
|
self.set_color_transform(gc_context, &*other.color_transform());
|
|
|
|
self.set_clip_depth(gc_context, other.clip_depth());
|
|
|
|
self.set_name(gc_context, &*other.name());
|
2019-12-09 22:19:35 +00:00
|
|
|
if let (Some(mut me), Some(other)) = (self.as_morph_shape(), other.as_morph_shape()) {
|
2019-12-07 02:29:36 +00:00
|
|
|
me.set_ratio(gc_context, other.ratio());
|
2019-09-16 16:13:12 +00:00
|
|
|
}
|
2019-12-01 19:24:47 +00:00
|
|
|
// onEnterFrame actions only apply to movie clips.
|
|
|
|
if let (Some(me), Some(other)) = (self.as_movie_clip(), other.as_movie_clip()) {
|
2019-12-02 00:07:13 +00:00
|
|
|
me.set_clip_actions(gc_context, other.clip_actions().iter().cloned().collect());
|
2019-12-01 19:24:47 +00:00
|
|
|
}
|
2019-09-16 16:13:12 +00:00
|
|
|
// TODO: More in here eventually.
|
|
|
|
}
|
|
|
|
|
2020-07-21 04:24:02 +00:00
|
|
|
fn object(&self) -> Avm1Value<'gc> {
|
|
|
|
Avm1Value::Undefined // todo: impl for every type and delete this fallback
|
|
|
|
}
|
|
|
|
|
|
|
|
fn object2(&self) -> Avm2Value<'gc> {
|
|
|
|
Avm2Value::Undefined // todo: see above
|
2019-08-28 23:29:43 +00:00
|
|
|
}
|
|
|
|
|
2021-04-10 02:30:54 +00:00
|
|
|
fn set_object2(&mut self, _mc: MutationContext<'gc, '_>, _to: Avm2Object<'gc>) {}
|
|
|
|
|
2019-12-19 19:51:56 +00:00
|
|
|
/// Tests if a given stage position point intersects with the world bounds of this object.
|
2020-09-03 00:01:10 +00:00
|
|
|
fn hit_test_bounds(&self, pos: (Twips, Twips)) -> bool {
|
|
|
|
self.world_bounds().contains(pos)
|
2020-08-21 06:25:31 +00:00
|
|
|
}
|
|
|
|
|
2020-12-04 23:34:04 +00:00
|
|
|
/// Tests if a given object's world bounds intersects with the world bounds
|
|
|
|
/// of this object.
|
2021-03-06 21:41:56 +00:00
|
|
|
fn hit_test_object(&self, other: DisplayObject<'gc>) -> bool {
|
|
|
|
self.world_bounds().intersects(&other.world_bounds())
|
2020-12-04 23:34:04 +00:00
|
|
|
}
|
|
|
|
|
2020-08-21 06:25:31 +00:00
|
|
|
/// Tests if a given stage position point intersects within this object, considering the art.
|
2020-09-14 02:17:58 +00:00
|
|
|
fn hit_test_shape(
|
|
|
|
&self,
|
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
pos: (Twips, Twips),
|
2021-02-18 23:12:11 +00:00
|
|
|
_options: HitTestOptions,
|
2020-09-14 02:17:58 +00:00
|
|
|
) -> bool {
|
|
|
|
// Default to using bounding box.
|
2021-03-06 21:41:56 +00:00
|
|
|
self.hit_test_bounds(pos)
|
2019-08-14 19:39:26 +00:00
|
|
|
}
|
|
|
|
|
2019-08-19 14:53:12 +00:00
|
|
|
fn mouse_pick(
|
|
|
|
&self,
|
2020-05-21 03:48:27 +00:00
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-12-07 02:29:36 +00:00
|
|
|
_self_node: DisplayObject<'gc>,
|
|
|
|
_pos: (Twips, Twips),
|
|
|
|
) -> Option<DisplayObject<'gc>> {
|
2019-08-19 14:53:12 +00:00
|
|
|
None
|
2019-08-14 19:39:26 +00:00
|
|
|
}
|
2019-08-28 23:29:43 +00:00
|
|
|
|
|
|
|
fn post_instantiation(
|
2020-11-25 00:35:15 +00:00
|
|
|
&self,
|
2020-08-31 19:30:17 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-12-07 02:29:36 +00:00
|
|
|
_display_object: DisplayObject<'gc>,
|
2020-07-21 04:24:02 +00:00
|
|
|
_init_object: Option<Avm1Object<'gc>>,
|
|
|
|
_instantiated_by: Instantiator,
|
2020-08-31 19:30:17 +00:00
|
|
|
run_frame: bool,
|
2019-08-28 23:29:43 +00:00
|
|
|
) {
|
2020-08-31 19:30:17 +00:00
|
|
|
if run_frame {
|
|
|
|
self.run_frame(context);
|
|
|
|
}
|
2019-08-28 23:29:43 +00:00
|
|
|
}
|
2019-10-09 23:46:15 +00:00
|
|
|
|
|
|
|
/// Return the version of the SWF that created this movie clip.
|
2019-10-13 21:27:04 +00:00
|
|
|
fn swf_version(&self) -> u8 {
|
|
|
|
self.parent()
|
2019-12-07 02:29:36 +00:00
|
|
|
.map(|p| p.swf_version())
|
2019-10-13 21:27:04 +00:00
|
|
|
.unwrap_or(NEWEST_PLAYER_VERSION)
|
|
|
|
}
|
2019-05-09 01:10:43 +00:00
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
/// Return the SWF that defines this display object.
|
|
|
|
fn movie(&self) -> Option<Arc<SwfMovie>> {
|
|
|
|
self.parent().and_then(|p| p.movie())
|
|
|
|
}
|
|
|
|
|
2021-02-03 03:19:24 +00:00
|
|
|
/// Return the VM that this object belongs to.
|
|
|
|
///
|
|
|
|
/// This function panics if the display object has no defining movie.
|
|
|
|
fn vm_type(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> AvmType {
|
|
|
|
let movie = self.movie().unwrap();
|
|
|
|
let library = context.library.library_for_movie_mut(movie);
|
|
|
|
|
|
|
|
library.avm_type()
|
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn instantiate(&self, gc_context: MutationContext<'gc, '_>) -> DisplayObject<'gc>;
|
|
|
|
fn as_ptr(&self) -> *const DisplayObjectPtr;
|
2020-02-01 08:38:44 +00:00
|
|
|
|
|
|
|
/// Whether this object can be used as a mask.
|
|
|
|
/// If this returns false and this object is used as a mask, the mask will not be applied.
|
|
|
|
/// This is used by movie clips to disable the mask when there are no children, for example.
|
|
|
|
fn allow_as_mask(&self) -> bool {
|
|
|
|
true
|
|
|
|
}
|
2020-01-30 04:21:06 +00:00
|
|
|
|
2020-10-29 21:14:00 +00:00
|
|
|
/// The cursor to use when this object is the hovered element under a mouse
|
|
|
|
fn mouse_cursor(&self) -> MouseCursor {
|
|
|
|
MouseCursor::Hand
|
|
|
|
}
|
|
|
|
|
2021-04-15 03:29:12 +00:00
|
|
|
/// Obtain the top-most non-Stage parent of the display tree hierarchy, if
|
2021-05-06 01:41:36 +00:00
|
|
|
/// a suitable object exists. If none such object exists, this function
|
|
|
|
/// yields an AVM1 error (which shouldn't happen in normal usage).
|
|
|
|
///
|
|
|
|
/// This function implements the AVM1 concept of root clips. For the AVM2
|
|
|
|
/// version, see `avm2_root`.
|
|
|
|
fn avm1_root(
|
|
|
|
&self,
|
|
|
|
context: &UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<DisplayObject<'gc>, Avm1Error<'gc>> {
|
2020-12-01 02:50:50 +00:00
|
|
|
let mut parent = if self.lock_root() {
|
|
|
|
None
|
|
|
|
} else {
|
2021-05-05 02:14:19 +00:00
|
|
|
self.avm1_parent()
|
2020-12-01 02:50:50 +00:00
|
|
|
};
|
2020-01-30 04:21:06 +00:00
|
|
|
|
|
|
|
while let Some(p) = parent {
|
2020-12-01 02:50:50 +00:00
|
|
|
if p.lock_root() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-05-05 02:14:19 +00:00
|
|
|
let grandparent = p.avm1_parent();
|
2020-01-30 04:21:06 +00:00
|
|
|
|
|
|
|
if grandparent.is_none() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
parent = grandparent;
|
|
|
|
}
|
|
|
|
|
2021-05-06 01:41:36 +00:00
|
|
|
parent
|
|
|
|
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
|
|
|
|
.or_else(|_| {
|
|
|
|
if let Avm1Value::Object(object) = self.object() {
|
|
|
|
object
|
|
|
|
.as_display_object()
|
|
|
|
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
|
|
|
|
} else if let Avm2Value::Object(object) = self.object2() {
|
|
|
|
if self.is_on_stage(context) {
|
|
|
|
object
|
|
|
|
.as_display_object()
|
|
|
|
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
|
|
|
|
} else {
|
|
|
|
Err(Avm1Error::InvalidDisplayObjectHierarchy)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(Avm1Error::InvalidDisplayObjectHierarchy)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Obtain the top-most non-Stage parent of the display tree hierarchy, if
|
|
|
|
/// a suitable object exists.
|
|
|
|
///
|
|
|
|
/// This function implements the AVM2 concept of root clips. For the AVM1
|
|
|
|
/// version, see `avm1_root`.
|
2021-05-06 22:43:23 +00:00
|
|
|
fn avm2_root(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> Option<DisplayObject<'gc>> {
|
2021-05-06 01:41:36 +00:00
|
|
|
let mut parent = Some((*self).into());
|
2021-05-07 00:00:58 +00:00
|
|
|
if self.as_stage().is_some() {
|
|
|
|
return parent;
|
|
|
|
}
|
2021-05-06 01:41:36 +00:00
|
|
|
|
|
|
|
while let Some(p) = parent {
|
2021-05-07 00:00:58 +00:00
|
|
|
let grandparent = p.parent();
|
2021-05-06 01:41:36 +00:00
|
|
|
|
|
|
|
if grandparent.is_none() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-05-07 00:00:58 +00:00
|
|
|
if let Some(gp_btn) = grandparent.and_then(|gp| gp.as_avm2_button()) {
|
|
|
|
let active_state = gp_btn.get_state_child(gp_btn.state().into());
|
|
|
|
if active_state
|
|
|
|
.map(|state| !DisplayObject::ptr_eq(state, p))
|
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-06 01:41:36 +00:00
|
|
|
if let Some(gp) = grandparent {
|
|
|
|
if gp.as_stage().is_some() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parent = grandparent;
|
|
|
|
}
|
|
|
|
|
2021-05-07 00:00:58 +00:00
|
|
|
let movie = self.movie()?;
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.library_for_movie_mut(movie)
|
|
|
|
.root()
|
|
|
|
.or_else(|| parent.filter(|p| p.is_on_stage(context)))
|
2020-01-30 04:21:06 +00:00
|
|
|
}
|
2020-06-18 01:18:40 +00:00
|
|
|
|
2021-05-06 21:32:08 +00:00
|
|
|
/// Obtain the root of the display tree hierarchy, if a suitable object
|
|
|
|
/// exists.
|
|
|
|
///
|
|
|
|
/// This implements the AVM2 concept of `stage`. Notably, it deliberately
|
|
|
|
/// will fail to locate the current player's stage for objects that are not
|
|
|
|
/// rooted to the DisplayObject hierarchy correctly. If you just want to
|
|
|
|
/// access the current player's stage, grab it from the context.
|
2021-05-06 22:47:38 +00:00
|
|
|
fn avm2_stage(&self, context: &UpdateContext<'_, 'gc, '_>) -> Option<DisplayObject<'gc>> {
|
2021-05-06 21:32:08 +00:00
|
|
|
let mut parent = Some((*self).into());
|
|
|
|
|
|
|
|
while let Some(p) = parent {
|
2021-05-07 00:00:58 +00:00
|
|
|
let grandparent = p.parent();
|
2021-05-06 21:32:08 +00:00
|
|
|
|
|
|
|
if grandparent.is_none() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-05-07 00:00:58 +00:00
|
|
|
if let Some(gp_btn) = grandparent.and_then(|gp| gp.as_avm2_button()) {
|
|
|
|
let active_state = gp_btn.get_state_child(gp_btn.state().into());
|
|
|
|
if active_state
|
|
|
|
.map(|state| !DisplayObject::ptr_eq(state, p))
|
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-06 21:32:08 +00:00
|
|
|
parent = grandparent;
|
|
|
|
}
|
|
|
|
|
2021-05-06 22:47:38 +00:00
|
|
|
Some(
|
|
|
|
parent
|
|
|
|
.filter(|p| p.as_stage().is_some())
|
|
|
|
.unwrap_or_else(|| context.stage.into()),
|
|
|
|
)
|
2021-05-06 21:32:08 +00:00
|
|
|
}
|
|
|
|
|
2021-01-16 20:53:35 +00:00
|
|
|
/// Determine if this display object is currently on the stage.
|
2021-01-16 22:23:43 +00:00
|
|
|
fn is_on_stage(self, context: &UpdateContext<'_, 'gc, '_>) -> bool {
|
2021-04-15 03:29:12 +00:00
|
|
|
let mut ancestor = self.avm2_parent();
|
2021-01-16 20:53:35 +00:00
|
|
|
while let Some(parent) = ancestor {
|
2021-04-15 03:29:12 +00:00
|
|
|
if parent.avm2_parent().is_some() {
|
|
|
|
ancestor = parent.avm2_parent();
|
2021-01-16 20:53:35 +00:00
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let ancestor = ancestor.unwrap_or_else(|| self.into());
|
2021-04-15 03:29:12 +00:00
|
|
|
DisplayObject::ptr_eq(ancestor, context.stage.into())
|
2021-01-16 20:53:35 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 01:18:40 +00:00
|
|
|
/// Assigns a default instance name `instanceN` to this object.
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_default_instance_name(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
2020-06-18 01:18:40 +00:00
|
|
|
if self.name().is_empty() {
|
|
|
|
let name = format!("instance{}", *context.instance_counter);
|
|
|
|
self.set_name(context.gc_context, &name);
|
|
|
|
*context.instance_counter = context.instance_counter.wrapping_add(1);
|
|
|
|
}
|
|
|
|
}
|
2020-06-23 04:07:27 +00:00
|
|
|
|
2020-11-28 18:03:53 +00:00
|
|
|
/// Assigns a default root name to this object.
|
|
|
|
///
|
|
|
|
/// The default root names change based on the AVM configuration of the
|
|
|
|
/// clip; AVM2 clips get `rootN` while AVM1 clips get blank strings.
|
|
|
|
fn set_default_root_name(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
2021-04-09 01:23:45 +00:00
|
|
|
let movie = self
|
|
|
|
.movie()
|
|
|
|
.expect("All roots should have an associated movie");
|
|
|
|
let vm_type = context.library.library_for_movie_mut(movie).avm_type();
|
|
|
|
|
|
|
|
if matches!(vm_type, AvmType::Avm2) {
|
2020-11-28 18:03:53 +00:00
|
|
|
self.set_name(context.gc_context, &format!("root{}", self.depth() + 1));
|
2021-04-09 01:23:45 +00:00
|
|
|
} else if matches!(vm_type, AvmType::Avm1) {
|
2020-11-28 18:03:53 +00:00
|
|
|
self.set_name(context.gc_context, "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-28 01:27:02 +00:00
|
|
|
fn bind_text_field_variables(&self, activation: &mut Activation<'_, 'gc, '_>) {
|
2020-06-23 04:07:27 +00:00
|
|
|
// Check all unbound text fields to see if they apply to this object.
|
|
|
|
// TODO: Replace with `Vec::drain_filter` when stable.
|
|
|
|
let mut i = 0;
|
2020-07-27 23:19:05 +00:00
|
|
|
let mut len = activation.context.unbound_text_fields.len();
|
2020-06-23 04:07:27 +00:00
|
|
|
while i < len {
|
2020-07-27 23:19:05 +00:00
|
|
|
if activation.context.unbound_text_fields[i]
|
|
|
|
.try_bind_text_field_variable(activation, false)
|
2020-06-28 10:07:27 +00:00
|
|
|
{
|
2020-07-27 23:19:05 +00:00
|
|
|
activation.context.unbound_text_fields.swap_remove(i);
|
2020-06-23 04:07:27 +00:00
|
|
|
len -= 1;
|
|
|
|
} else {
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-29 05:55:44 +00:00
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
pub enum DisplayObjectPtr {}
|
|
|
|
|
2020-05-25 19:05:44 +00:00
|
|
|
// To use this macro: `use crate::impl_display_object_sansbounds;` or `use crate::prelude::*;`
|
2019-10-26 22:04:52 +00:00
|
|
|
#[macro_export]
|
2020-05-25 19:05:44 +00:00
|
|
|
macro_rules! impl_display_object_sansbounds {
|
2019-05-07 10:34:17 +00:00
|
|
|
($field:ident) => {
|
2019-09-08 02:33:06 +00:00
|
|
|
fn depth(&self) -> crate::prelude::Depth {
|
2019-12-07 02:29:36 +00:00
|
|
|
self.0.read().$field.depth()
|
2019-09-08 02:33:06 +00:00
|
|
|
}
|
2019-12-17 09:51:00 +00:00
|
|
|
fn set_depth(&self, gc_context: gc_arena::MutationContext<'gc, '_>, depth: Depth) {
|
|
|
|
self.0.write(gc_context).$field.set_depth(depth)
|
|
|
|
}
|
2019-09-16 16:13:12 +00:00
|
|
|
fn place_frame(&self) -> u16 {
|
2019-12-07 02:29:36 +00:00
|
|
|
self.0.read().$field.place_frame()
|
2019-09-16 16:13:12 +00:00
|
|
|
}
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_place_frame(&self, context: gc_arena::MutationContext<'gc, '_>, frame: u16) {
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_place_frame(frame)
|
2019-09-16 16:13:12 +00:00
|
|
|
}
|
2019-12-07 02:29:36 +00:00
|
|
|
fn transform(&self) -> std::cell::Ref<crate::transform::Transform> {
|
|
|
|
std::cell::Ref::map(self.0.read(), |o| o.$field.transform())
|
2019-05-09 01:10:43 +00:00
|
|
|
}
|
2020-05-19 16:50:37 +00:00
|
|
|
fn matrix(&self) -> std::cell::Ref<swf::Matrix> {
|
2019-12-07 02:29:36 +00:00
|
|
|
std::cell::Ref::map(self.0.read(), |o| o.$field.matrix())
|
2019-05-09 01:10:43 +00:00
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn matrix_mut(
|
2020-08-22 05:59:45 +00:00
|
|
|
&self,
|
2020-04-25 05:16:16 +00:00
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
2020-05-19 16:50:37 +00:00
|
|
|
) -> std::cell::RefMut<swf::Matrix> {
|
2021-02-05 16:04:23 +00:00
|
|
|
std::cell::RefMut::map(self.0.write(context), |o| o.$field.matrix_mut())
|
2019-08-15 21:42:45 +00:00
|
|
|
}
|
2019-12-07 02:29:36 +00:00
|
|
|
fn color_transform(&self) -> std::cell::Ref<crate::color_transform::ColorTransform> {
|
|
|
|
std::cell::Ref::map(self.0.read(), |o| o.$field.color_transform())
|
2019-05-09 01:10:43 +00:00
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn color_transform_mut(
|
|
|
|
&self,
|
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
) -> std::cell::RefMut<crate::color_transform::ColorTransform> {
|
2019-12-04 01:52:00 +00:00
|
|
|
std::cell::RefMut::map(self.0.write(context), |o| o.$field.color_transform_mut())
|
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn set_color_transform(
|
2020-08-22 05:59:45 +00:00
|
|
|
&self,
|
2020-04-25 05:16:16 +00:00
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
color_transform: &crate::color_transform::ColorTransform,
|
|
|
|
) {
|
|
|
|
self.0
|
|
|
|
.write(context)
|
|
|
|
.$field
|
2021-02-05 16:04:23 +00:00
|
|
|
.set_color_transform(color_transform)
|
2019-05-09 01:10:43 +00:00
|
|
|
}
|
2020-09-25 23:18:31 +00:00
|
|
|
fn rotation(&self, gc_context: gc_arena::MutationContext<'gc, '_>) -> Degrees {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.0.write(gc_context).$field.rotation()
|
|
|
|
}
|
2020-09-25 23:18:31 +00:00
|
|
|
fn set_rotation(&self, gc_context: gc_arena::MutationContext<'gc, '_>, degrees: Degrees) {
|
|
|
|
self.0.write(gc_context).$field.set_rotation(degrees)
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2020-09-25 03:17:07 +00:00
|
|
|
fn scale_x(&self, gc_context: gc_arena::MutationContext<'gc, '_>) -> Percent {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.0.write(gc_context).$field.scale_x()
|
|
|
|
}
|
2020-09-25 03:17:07 +00:00
|
|
|
fn set_scale_x(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: Percent) {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.0.write(gc_context).$field.set_scale_x(value)
|
|
|
|
}
|
2020-09-25 03:17:07 +00:00
|
|
|
fn scale_y(&self, gc_context: gc_arena::MutationContext<'gc, '_>) -> Percent {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.0.write(gc_context).$field.scale_y()
|
|
|
|
}
|
2020-09-25 03:17:07 +00:00
|
|
|
fn set_scale_y(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: Percent) {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.0.write(gc_context).$field.set_scale_y(value)
|
|
|
|
}
|
|
|
|
fn alpha(&self) -> f64 {
|
|
|
|
self.0.read().$field.alpha()
|
|
|
|
}
|
|
|
|
fn set_alpha(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: f64) {
|
|
|
|
self.0.write(gc_context).$field.set_alpha(value)
|
|
|
|
}
|
2019-12-07 02:29:36 +00:00
|
|
|
fn name(&self) -> std::cell::Ref<str> {
|
|
|
|
std::cell::Ref::map(self.0.read(), |o| o.$field.name())
|
2019-05-09 01:10:43 +00:00
|
|
|
}
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_name(&self, context: gc_arena::MutationContext<'gc, '_>, name: &str) {
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_name(name)
|
2019-05-09 01:10:43 +00:00
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
fn clip_depth(&self) -> crate::prelude::Depth {
|
2019-12-07 02:29:36 +00:00
|
|
|
self.0.read().$field.clip_depth()
|
2019-05-12 17:48:00 +00:00
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn set_clip_depth(
|
2020-08-22 05:59:45 +00:00
|
|
|
&self,
|
2020-04-25 05:16:16 +00:00
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
depth: crate::prelude::Depth,
|
|
|
|
) {
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_clip_depth(depth)
|
2019-05-12 17:48:00 +00:00
|
|
|
}
|
2021-05-05 02:14:19 +00:00
|
|
|
fn parent(&self) -> Option<crate::display_object::DisplayObject<'gc>> {
|
|
|
|
self.0.read().$field.parent()
|
2019-08-13 02:00:12 +00:00
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn set_parent(
|
2020-08-17 22:01:58 +00:00
|
|
|
&self,
|
2020-04-25 05:16:16 +00:00
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
parent: Option<crate::display_object::DisplayObject<'gc>>,
|
|
|
|
) {
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_parent(parent)
|
2019-08-13 02:00:12 +00:00
|
|
|
}
|
2019-12-07 02:29:36 +00:00
|
|
|
fn prev_sibling(&self) -> Option<DisplayObject<'gc>> {
|
|
|
|
self.0.read().$field.prev_sibling()
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn set_prev_sibling(
|
2020-08-17 22:01:58 +00:00
|
|
|
&self,
|
2020-04-25 05:16:16 +00:00
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
node: Option<DisplayObject<'gc>>,
|
|
|
|
) {
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_prev_sibling(node);
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2019-12-07 02:29:36 +00:00
|
|
|
fn next_sibling(&self) -> Option<DisplayObject<'gc>> {
|
|
|
|
self.0.read().$field.next_sibling()
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn set_next_sibling(
|
2020-08-17 22:01:58 +00:00
|
|
|
&self,
|
2020-04-25 05:16:16 +00:00
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
node: Option<DisplayObject<'gc>>,
|
|
|
|
) {
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_next_sibling(node);
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2020-11-04 07:30:48 +00:00
|
|
|
fn masker(&self) -> Option<DisplayObject<'gc>> {
|
|
|
|
self.0.read().$field.masker()
|
|
|
|
}
|
|
|
|
fn set_masker(
|
|
|
|
&self,
|
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
node: Option<DisplayObject<'gc>>,
|
|
|
|
remove_old_link: bool,
|
|
|
|
) {
|
|
|
|
if remove_old_link {
|
|
|
|
if let Some(old_masker) = self.0.read().$field.masker() {
|
|
|
|
old_masker.set_maskee(context, None, false);
|
|
|
|
}
|
|
|
|
}
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_masker(node);
|
2020-11-04 07:30:48 +00:00
|
|
|
}
|
|
|
|
fn maskee(&self) -> Option<DisplayObject<'gc>> {
|
|
|
|
self.0.read().$field.maskee()
|
|
|
|
}
|
|
|
|
fn set_maskee(
|
|
|
|
&self,
|
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
node: Option<DisplayObject<'gc>>,
|
|
|
|
remove_old_link: bool,
|
|
|
|
) {
|
|
|
|
if remove_old_link {
|
|
|
|
if let Some(old_maskee) = self.0.read().$field.maskee() {
|
|
|
|
old_maskee.set_masker(context, None, false);
|
|
|
|
}
|
|
|
|
}
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_maskee(node);
|
2020-11-04 07:30:48 +00:00
|
|
|
}
|
2019-10-18 04:10:13 +00:00
|
|
|
fn removed(&self) -> bool {
|
2019-12-07 02:29:36 +00:00
|
|
|
self.0.read().$field.removed()
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_removed(&self, context: gc_arena::MutationContext<'gc, '_>, value: bool) {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.0.write(context).$field.set_removed(value)
|
|
|
|
}
|
2021-01-24 08:16:07 +00:00
|
|
|
fn sound_transform(&self) -> std::cell::Ref<crate::display_object::SoundTransform> {
|
|
|
|
std::cell::Ref::map(self.0.read(), |r| r.$field.sound_transform())
|
|
|
|
}
|
|
|
|
fn set_sound_transform(
|
|
|
|
&self,
|
|
|
|
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
|
|
|
|
value: crate::display_object::SoundTransform,
|
|
|
|
) {
|
|
|
|
self.0
|
|
|
|
.write(context.gc_context)
|
|
|
|
.$field
|
|
|
|
.set_sound_transform(value);
|
2021-01-27 21:48:52 +00:00
|
|
|
context.set_sound_transforms_dirty();
|
2021-01-24 08:16:07 +00:00
|
|
|
}
|
2019-12-04 01:52:00 +00:00
|
|
|
fn visible(&self) -> bool {
|
|
|
|
self.0.read().$field.visible()
|
|
|
|
}
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_visible(&self, context: gc_arena::MutationContext<'gc, '_>, value: bool) {
|
2019-12-04 01:52:00 +00:00
|
|
|
self.0.write(context).$field.set_visible(value);
|
2019-05-09 01:10:43 +00:00
|
|
|
}
|
2020-12-01 02:50:50 +00:00
|
|
|
fn lock_root(&self) -> bool {
|
|
|
|
self.0.read().$field.lock_root()
|
|
|
|
}
|
|
|
|
fn set_lock_root(&self, context: gc_arena::MutationContext<'gc, '_>, value: bool) {
|
|
|
|
self.0.write(context).$field.set_lock_root(value);
|
|
|
|
}
|
2019-12-15 17:32:42 +00:00
|
|
|
fn transformed_by_script(&self) -> bool {
|
|
|
|
self.0.read().$field.transformed_by_script()
|
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn set_transformed_by_script(
|
|
|
|
&self,
|
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
value: bool,
|
|
|
|
) {
|
|
|
|
self.0
|
|
|
|
.write(context)
|
|
|
|
.$field
|
|
|
|
.set_transformed_by_script(value)
|
2019-12-15 17:32:42 +00:00
|
|
|
}
|
2020-10-24 02:19:30 +00:00
|
|
|
fn placed_by_script(&self) -> bool {
|
|
|
|
self.0.read().$field.placed_by_script()
|
|
|
|
}
|
|
|
|
fn set_placed_by_script(&self, context: gc_arena::MutationContext<'gc, '_>, value: bool) {
|
|
|
|
self.0.write(context).$field.set_placed_by_script(value)
|
|
|
|
}
|
2020-11-28 19:15:30 +00:00
|
|
|
fn instantiated_by_timeline(&self) -> bool {
|
|
|
|
self.0.read().$field.instantiated_by_timeline()
|
|
|
|
}
|
|
|
|
fn set_instantiated_by_timeline(
|
|
|
|
&self,
|
|
|
|
context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
value: bool,
|
|
|
|
) {
|
|
|
|
self.0
|
|
|
|
.write(context)
|
|
|
|
.$field
|
|
|
|
.set_instantiated_by_timeline(value)
|
|
|
|
}
|
2020-04-25 05:16:16 +00:00
|
|
|
fn instantiate(
|
|
|
|
&self,
|
|
|
|
gc_context: gc_arena::MutationContext<'gc, '_>,
|
|
|
|
) -> crate::display_object::DisplayObject<'gc> {
|
|
|
|
Self(gc_arena::GcCell::allocate(
|
|
|
|
gc_context,
|
|
|
|
self.0.read().clone(),
|
|
|
|
))
|
|
|
|
.into()
|
2019-12-07 02:29:36 +00:00
|
|
|
}
|
|
|
|
fn as_ptr(&self) -> *const crate::display_object::DisplayObjectPtr {
|
|
|
|
self.0.as_ptr() as *const crate::display_object::DisplayObjectPtr
|
2019-10-09 23:46:15 +00:00
|
|
|
}
|
2019-04-29 05:55:44 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:05:44 +00:00
|
|
|
// To use this macro: `use crate::impl_display_object;` or `use crate::prelude::*;`
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! impl_display_object {
|
|
|
|
($field:ident) => {
|
|
|
|
impl_display_object_sansbounds!($field);
|
|
|
|
|
|
|
|
fn x(&self) -> f64 {
|
|
|
|
self.0.read().$field.x()
|
|
|
|
}
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_x(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: f64) {
|
2020-05-25 19:05:44 +00:00
|
|
|
self.0.write(gc_context).$field.set_x(value)
|
|
|
|
}
|
|
|
|
fn y(&self) -> f64 {
|
|
|
|
self.0.read().$field.y()
|
|
|
|
}
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_y(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: f64) {
|
2020-05-25 19:05:44 +00:00
|
|
|
self.0.write(gc_context).$field.set_y(value)
|
|
|
|
}
|
2020-08-22 05:59:45 +00:00
|
|
|
fn set_matrix(&self, context: gc_arena::MutationContext<'gc, '_>, matrix: &swf::Matrix) {
|
2021-02-05 16:04:23 +00:00
|
|
|
self.0.write(context).$field.set_matrix(matrix)
|
2020-05-26 00:01:49 +00:00
|
|
|
}
|
2020-05-25 19:05:44 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
impl<'gc> DisplayObject<'gc> {
|
|
|
|
pub fn ptr_eq(a: DisplayObject<'gc>, b: DisplayObject<'gc>) -> bool {
|
|
|
|
a.as_ptr() == b.as_ptr()
|
|
|
|
}
|
|
|
|
}
|
2019-10-18 04:10:13 +00:00
|
|
|
|
2021-01-22 00:35:46 +00:00
|
|
|
bitflags! {
|
|
|
|
/// Bit flags used by `DisplayObject`.
|
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
struct DisplayObjectFlags: u8 {
|
|
|
|
/// Whether this object has been removed from the display list.
|
|
|
|
/// Necessary in AVM1 to throw away queued actions from removed movie clips.
|
|
|
|
const REMOVED = 1 << 0;
|
2019-12-04 01:52:00 +00:00
|
|
|
|
2021-01-22 00:35:46 +00:00
|
|
|
/// If this object is visible (`_visible` property).
|
|
|
|
const VISIBLE = 1 << 1;
|
2019-12-04 01:52:00 +00:00
|
|
|
|
2021-01-22 00:35:46 +00:00
|
|
|
/// Whether the `_xscale`, `_yscale` and `_rotation` of the object have been calculated and cached.
|
|
|
|
const SCALE_ROTATION_CACHED = 1 << 2;
|
2019-12-15 17:32:42 +00:00
|
|
|
|
2021-01-22 00:35:46 +00:00
|
|
|
/// Whether this object has been transformed by ActionScript.
|
|
|
|
/// When this flag is set, changes from SWF `PlaceObject` tags are ignored.
|
|
|
|
const TRANSFORMED_BY_SCRIPT = 1 << 3;
|
2020-10-24 02:19:30 +00:00
|
|
|
|
2021-01-22 00:35:46 +00:00
|
|
|
/// Whether this object has been placed in a container by ActionScript 3.
|
|
|
|
/// When this flag is set, changes from SWF `RemoveObject` tags are ignored.
|
|
|
|
const PLACED_BY_SCRIPT = 1 << 4;
|
2020-11-28 19:15:30 +00:00
|
|
|
|
2021-01-22 00:35:46 +00:00
|
|
|
/// Whether this object has been instantiated by a SWF tag.
|
|
|
|
/// When this flag is set, changes from SWF `RemoveObject` tags are ignored.
|
|
|
|
const INSTANTIATED_BY_TIMELINE = 1 << 5;
|
2020-12-01 02:50:50 +00:00
|
|
|
|
2021-01-22 00:35:46 +00:00
|
|
|
/// Whether this object has `_lockroot` set to true, in which case
|
|
|
|
/// it becomes the _root of itself and of any children
|
|
|
|
const LOCK_ROOT = 1 << 6;
|
|
|
|
}
|
2019-12-04 01:52:00 +00:00
|
|
|
}
|
2021-01-24 08:16:07 +00:00
|
|
|
|
2021-05-13 05:33:04 +00:00
|
|
|
bitflags! {
|
|
|
|
/// Defines how hit testing should be performed.
|
|
|
|
/// Used for mouse picking and ActionScript's hitTestClip functions.
|
|
|
|
pub struct HitTestOptions: u8 {
|
|
|
|
/// Ignore objects used as masks (setMask / clipDepth).
|
|
|
|
const SKIP_MASK = 1 << 0;
|
|
|
|
|
|
|
|
/// Ignore objects with the ActionScript's visibility flag turned off.
|
|
|
|
const SKIP_INVISIBLE = 1 << 1;
|
|
|
|
|
|
|
|
/// The options used for `hitTest` calls in ActionScript.
|
|
|
|
const AVM_HIT_TEST = Self::SKIP_MASK.bits;
|
|
|
|
|
|
|
|
/// The options used for mouse picking, such as clicking on buttons.
|
|
|
|
const MOUSE_PICK = Self::SKIP_MASK.bits | Self::SKIP_INVISIBLE.bits;
|
|
|
|
}
|
2021-02-18 23:12:11 +00:00
|
|
|
}
|
|
|
|
|
2021-01-24 08:16:07 +00:00
|
|
|
/// Represents the sound transfomr of sounds played inside a Flash MovieClip.
|
|
|
|
/// Every value is a percentage (0-100), but out of range values are allowed.
|
|
|
|
/// In AVM1, this is returned by `Sound.getTransform`.
|
|
|
|
/// In AVM2, this is returned by `Sprite.soundTransform`.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Collect)]
|
|
|
|
#[collect(require_static)]
|
2021-01-24 08:16:07 +00:00
|
|
|
pub struct SoundTransform {
|
|
|
|
pub volume: i32,
|
|
|
|
pub left_to_left: i32,
|
|
|
|
pub left_to_right: i32,
|
|
|
|
pub right_to_left: i32,
|
|
|
|
pub right_to_right: i32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SoundTransform {
|
|
|
|
pub const MAX_VOLUME: i32 = 100;
|
|
|
|
|
|
|
|
/// Applies another SoundTransform on top of this SoundTransform.
|
|
|
|
pub fn concat(&mut self, other: &SoundTransform) {
|
|
|
|
// This is a 2x2 matrix multiply between the transforms.
|
|
|
|
// Done with integer math to match Flash behavior.
|
|
|
|
const MAX_VOLUME: i64 = SoundTransform::MAX_VOLUME as i64;
|
|
|
|
self.volume = (i64::from(self.volume) * i64::from(other.volume) / MAX_VOLUME) as i32;
|
|
|
|
|
|
|
|
let ll0 = i64::from(self.left_to_left);
|
|
|
|
let lr0 = i64::from(self.left_to_right);
|
|
|
|
let rl0 = i64::from(self.right_to_left);
|
|
|
|
let rr0 = i64::from(self.right_to_right);
|
|
|
|
let ll1 = i64::from(other.left_to_left);
|
|
|
|
let lr1 = i64::from(other.left_to_right);
|
|
|
|
let rl1 = i64::from(other.right_to_left);
|
|
|
|
let rr1 = i64::from(other.right_to_right);
|
|
|
|
self.left_to_left = ((ll0 * ll1 + rl0 * lr1) / MAX_VOLUME) as i32;
|
|
|
|
self.left_to_right = ((lr0 * ll1 + rr0 * lr1) / MAX_VOLUME) as i32;
|
|
|
|
self.right_to_left = ((ll0 * rl1 + rl0 * rr1) / MAX_VOLUME) as i32;
|
|
|
|
self.right_to_right = ((lr0 * rl1 + rr0 * rr1) / MAX_VOLUME) as i32;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the pan of this transform.
|
|
|
|
/// -100 is full left and 100 is full right.
|
|
|
|
/// This matches the behavior of AVM1 `Sound.getPan()`
|
|
|
|
pub fn pan(&self) -> i32 {
|
|
|
|
// It's not clear why Flash has the weird `abs` behavior, but this
|
|
|
|
// mathes the values that Flash returns (see `sound` regression test).
|
|
|
|
if self.left_to_left != Self::MAX_VOLUME {
|
|
|
|
Self::MAX_VOLUME - self.left_to_left.abs()
|
|
|
|
} else {
|
|
|
|
self.right_to_right.abs() - Self::MAX_VOLUME
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets this transform of this pan.
|
|
|
|
/// -100 is full left and 100 is full right.
|
|
|
|
/// This matches the behavior of AVM1 `Sound.setPan()`.
|
|
|
|
pub fn set_pan(&mut self, pan: i32) {
|
|
|
|
if pan >= 0 {
|
|
|
|
self.left_to_left = Self::MAX_VOLUME - pan;
|
|
|
|
self.right_to_right = Self::MAX_VOLUME;
|
|
|
|
} else {
|
|
|
|
self.left_to_left = Self::MAX_VOLUME;
|
|
|
|
self.right_to_right = Self::MAX_VOLUME + pan;
|
|
|
|
}
|
|
|
|
self.left_to_right = 0;
|
|
|
|
self.right_to_left = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for SoundTransform {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
volume: 100,
|
|
|
|
left_to_left: 100,
|
|
|
|
left_to_right: 0,
|
|
|
|
right_to_left: 0,
|
|
|
|
right_to_right: 100,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|