avm2: Implement `MovieClip.scenes`.
This commit is contained in:
parent
c682868205
commit
8152400a39
|
@ -11,7 +11,7 @@ use crate::avm2::string::AvmString;
|
||||||
use crate::avm2::traits::Trait;
|
use crate::avm2::traits::Trait;
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
use crate::avm2::Error;
|
use crate::avm2::Error;
|
||||||
use crate::display_object::{MovieClip, TDisplayObject};
|
use crate::display_object::{MovieClip, Scene, TDisplayObject};
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use gc_arena::{GcCell, MutationContext};
|
||||||
|
|
||||||
/// Implements `flash.display.MovieClip`'s instance constructor.
|
/// Implements `flash.display.MovieClip`'s instance constructor.
|
||||||
|
@ -66,8 +66,13 @@ pub fn current_frame<'gc>(
|
||||||
.and_then(|o| o.as_display_object())
|
.and_then(|o| o.as_display_object())
|
||||||
.and_then(|dobj| dobj.as_movie_clip())
|
.and_then(|dobj| dobj.as_movie_clip())
|
||||||
{
|
{
|
||||||
if let Some((_scene, scene_basis, _length)) = mc.current_scene() {
|
if let Some(Scene {
|
||||||
return Ok(((mc.current_frame() + 1) - scene_basis).into());
|
name: _,
|
||||||
|
start,
|
||||||
|
length: _,
|
||||||
|
}) = mc.current_scene()
|
||||||
|
{
|
||||||
|
return Ok(((mc.current_frame() + 1) - start).into());
|
||||||
} else {
|
} else {
|
||||||
return Ok(mc.current_frame().into());
|
return Ok(mc.current_frame().into());
|
||||||
}
|
}
|
||||||
|
@ -122,16 +127,24 @@ pub fn current_label<'gc>(
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a scene, produce it's name, length, and a list of frame labels.
|
||||||
|
///
|
||||||
|
/// The intended purpose of this output is to be sent directly into the
|
||||||
|
/// constructor of `flash.display.Scene`.
|
||||||
fn labels_for_scene<'gc>(
|
fn labels_for_scene<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
mc: MovieClip<'gc>,
|
mc: MovieClip<'gc>,
|
||||||
|
scene: &Scene,
|
||||||
) -> Result<(String, u16, Object<'gc>), Error> {
|
) -> Result<(String, u16, Object<'gc>), Error> {
|
||||||
let (scene_name, scene_start, scene_length) =
|
let Scene {
|
||||||
mc.current_scene().unwrap_or(("".to_string(), 0, u16::MAX));
|
name: scene_name,
|
||||||
|
start: scene_start,
|
||||||
|
length: scene_length,
|
||||||
|
} = scene;
|
||||||
let mut frame_labels = Vec::new();
|
let mut frame_labels = Vec::new();
|
||||||
let frame_label_proto = activation.context.avm2.prototypes().framelabel;
|
let frame_label_proto = activation.context.avm2.prototypes().framelabel;
|
||||||
|
|
||||||
for (name, frame) in mc.labels_in_range(scene_start, scene_start + scene_length) {
|
for (name, frame) in mc.labels_in_range(*scene_start, scene_start + scene_length) {
|
||||||
let name: Value<'gc> = AvmString::new(activation.context.gc_context, name).into();
|
let name: Value<'gc> = AvmString::new(activation.context.gc_context, name).into();
|
||||||
let local_frame = frame - scene_start + 1;
|
let local_frame = frame - scene_start + 1;
|
||||||
let args = [name, local_frame.into()];
|
let args = [name, local_frame.into()];
|
||||||
|
@ -143,8 +156,8 @@ fn labels_for_scene<'gc>(
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
scene_name,
|
scene_name.to_string(),
|
||||||
scene_length,
|
*scene_length,
|
||||||
ArrayObject::from_array(
|
ArrayObject::from_array(
|
||||||
ArrayStorage::from_storage(frame_labels),
|
ArrayStorage::from_storage(frame_labels),
|
||||||
activation.context.avm2.prototypes().array,
|
activation.context.avm2.prototypes().array,
|
||||||
|
@ -163,7 +176,8 @@ pub fn current_labels<'gc>(
|
||||||
.and_then(|o| o.as_display_object())
|
.and_then(|o| o.as_display_object())
|
||||||
.and_then(|dobj| dobj.as_movie_clip())
|
.and_then(|dobj| dobj.as_movie_clip())
|
||||||
{
|
{
|
||||||
return Ok(labels_for_scene(activation, mc)?.2.into());
|
let scene = mc.current_scene().unwrap_or_else(Default::default);
|
||||||
|
return Ok(labels_for_scene(activation, mc, &scene)?.2.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
@ -179,7 +193,8 @@ pub fn current_scene<'gc>(
|
||||||
.and_then(|o| o.as_display_object())
|
.and_then(|o| o.as_display_object())
|
||||||
.and_then(|dobj| dobj.as_movie_clip())
|
.and_then(|dobj| dobj.as_movie_clip())
|
||||||
{
|
{
|
||||||
let (scene_name, scene_length, scene_labels) = labels_for_scene(activation, mc)?;
|
let scene = mc.current_scene().unwrap_or_else(Default::default);
|
||||||
|
let (scene_name, scene_length, scene_labels) = labels_for_scene(activation, mc, &scene)?;
|
||||||
let scene_proto = activation.context.avm2.prototypes().scene;
|
let scene_proto = activation.context.avm2.prototypes().scene;
|
||||||
let args = [
|
let args = [
|
||||||
AvmString::new(activation.context.gc_context, scene_name).into(),
|
AvmString::new(activation.context.gc_context, scene_name).into(),
|
||||||
|
@ -197,6 +212,46 @@ pub fn current_scene<'gc>(
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements `scenes`.
|
||||||
|
pub fn scenes<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
if let Some(mc) = this
|
||||||
|
.and_then(|o| o.as_display_object())
|
||||||
|
.and_then(|dobj| dobj.as_movie_clip())
|
||||||
|
{
|
||||||
|
let mut scene_objects = Vec::new();
|
||||||
|
|
||||||
|
for scene in mc.scenes() {
|
||||||
|
let (scene_name, scene_length, scene_labels) =
|
||||||
|
labels_for_scene(activation, mc, &scene)?;
|
||||||
|
let scene_proto = activation.context.avm2.prototypes().scene;
|
||||||
|
let args = [
|
||||||
|
AvmString::new(activation.context.gc_context, scene_name).into(),
|
||||||
|
scene_labels.into(),
|
||||||
|
scene_length.into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let scene = scene_proto.construct(activation, &args)?;
|
||||||
|
|
||||||
|
scene::instance_init(activation, Some(scene), &args)?;
|
||||||
|
|
||||||
|
scene_objects.push(Some(scene.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ArrayObject::from_array(
|
||||||
|
ArrayStorage::from_storage(scene_objects),
|
||||||
|
activation.context.avm2.prototypes().array,
|
||||||
|
activation.context.gc_context,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
|
||||||
/// Implements `framesLoaded`.
|
/// Implements `framesLoaded`.
|
||||||
pub fn frames_loaded<'gc>(
|
pub fn frames_loaded<'gc>(
|
||||||
_activation: &mut Activation<'_, 'gc, '_>,
|
_activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
@ -398,8 +453,13 @@ pub fn prev_scene<'gc>(
|
||||||
.and_then(|o| o.as_display_object())
|
.and_then(|o| o.as_display_object())
|
||||||
.and_then(|dobj| dobj.as_movie_clip())
|
.and_then(|dobj| dobj.as_movie_clip())
|
||||||
{
|
{
|
||||||
if let Some((_scene, target_frame, _length)) = mc.previous_scene() {
|
if let Some(Scene {
|
||||||
mc.goto_frame(&mut activation.context, target_frame, false);
|
name: _,
|
||||||
|
start,
|
||||||
|
length: _,
|
||||||
|
}) = mc.previous_scene()
|
||||||
|
{
|
||||||
|
mc.goto_frame(&mut activation.context, start, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,8 +476,13 @@ pub fn next_scene<'gc>(
|
||||||
.and_then(|o| o.as_display_object())
|
.and_then(|o| o.as_display_object())
|
||||||
.and_then(|dobj| dobj.as_movie_clip())
|
.and_then(|dobj| dobj.as_movie_clip())
|
||||||
{
|
{
|
||||||
if let Some((_scene, target_frame, _length)) = mc.next_scene() {
|
if let Some(Scene {
|
||||||
mc.goto_frame(&mut activation.context, target_frame, false);
|
name: _,
|
||||||
|
start,
|
||||||
|
length: _,
|
||||||
|
}) = mc.next_scene()
|
||||||
|
{
|
||||||
|
mc.goto_frame(&mut activation.context, start, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,6 +531,11 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
|
||||||
Method::from_builtin(current_scene),
|
Method::from_builtin(current_scene),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
write.define_instance_trait(Trait::from_getter(
|
||||||
|
QName::new(Namespace::package(""), "scenes"),
|
||||||
|
Method::from_builtin(scenes),
|
||||||
|
));
|
||||||
|
|
||||||
write.define_instance_trait(Trait::from_getter(
|
write.define_instance_trait(Trait::from_getter(
|
||||||
QName::new(Namespace::package(""), "framesLoaded"),
|
QName::new(Namespace::package(""), "framesLoaded"),
|
||||||
Method::from_builtin(frames_loaded),
|
Method::from_builtin(frames_loaded),
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub use button::Button;
|
||||||
pub use edit_text::{AutoSizeMode, EditText};
|
pub use edit_text::{AutoSizeMode, EditText};
|
||||||
pub use graphic::Graphic;
|
pub use graphic::Graphic;
|
||||||
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
||||||
pub use movie_clip::MovieClip;
|
pub use movie_clip::{MovieClip, Scene};
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -554,9 +554,6 @@ impl<'gc> MovieClip<'gc> {
|
||||||
static_data: &mut MovieClipStatic,
|
static_data: &mut MovieClipStatic,
|
||||||
) -> DecodeResult {
|
) -> DecodeResult {
|
||||||
let mut sfl_data = reader.read_define_scene_and_frame_label_data()?;
|
let mut sfl_data = reader.read_define_scene_and_frame_label_data()?;
|
||||||
sfl_data
|
|
||||||
.frame_labels
|
|
||||||
.sort_unstable_by(|s1, s2| s1.frame_num.cmp(&s2.frame_num));
|
|
||||||
sfl_data
|
sfl_data
|
||||||
.scenes
|
.scenes
|
||||||
.sort_unstable_by(|s1, s2| s1.frame_num.cmp(&s2.frame_num));
|
.sort_unstable_by(|s1, s2| s1.frame_num.cmp(&s2.frame_num));
|
||||||
|
@ -640,60 +637,136 @@ impl<'gc> MovieClip<'gc> {
|
||||||
self.0.read().current_frame
|
self.0.read().current_frame
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_scene(self) -> Option<(String, FrameNumber, FrameNumber)> {
|
/// Return the current scene.
|
||||||
|
pub fn current_scene(self) -> Option<Scene> {
|
||||||
let current_frame = self.0.read().current_frame();
|
let current_frame = self.0.read().current_frame();
|
||||||
|
|
||||||
self.filter_scenes(|best, (_scene, frame, _end)| {
|
self.filter_scenes(
|
||||||
frame <= current_frame && best.map(|v| frame >= v.1).unwrap_or(true)
|
|best,
|
||||||
})
|
Scene {
|
||||||
|
name: _,
|
||||||
|
start,
|
||||||
|
length: _,
|
||||||
|
}| {
|
||||||
|
*start <= current_frame
|
||||||
|
&& best
|
||||||
|
.map(
|
||||||
|
|Scene {
|
||||||
|
name: _,
|
||||||
|
start: best_start,
|
||||||
|
length: _,
|
||||||
|
}| start >= best_start,
|
||||||
|
)
|
||||||
|
.unwrap_or(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous_scene(self) -> Option<(String, FrameNumber, FrameNumber)> {
|
/// Return the previous scene.
|
||||||
|
pub fn previous_scene(self) -> Option<Scene> {
|
||||||
let current_frame = self
|
let current_frame = self
|
||||||
.current_scene()
|
.current_scene()
|
||||||
.map(|v| v.1)
|
.map(
|
||||||
|
|Scene {
|
||||||
|
name: _,
|
||||||
|
start,
|
||||||
|
length: _,
|
||||||
|
}| start,
|
||||||
|
)
|
||||||
.unwrap_or_else(|| self.current_frame());
|
.unwrap_or_else(|| self.current_frame());
|
||||||
|
|
||||||
self.filter_scenes(|best, (_scene, frame, _end)| {
|
self.filter_scenes(
|
||||||
frame < current_frame && best.map(|v| frame >= v.1).unwrap_or(true)
|
|best,
|
||||||
})
|
Scene {
|
||||||
|
name: _,
|
||||||
|
start,
|
||||||
|
length: _,
|
||||||
|
}| {
|
||||||
|
*start < current_frame
|
||||||
|
&& best
|
||||||
|
.map(
|
||||||
|
|Scene {
|
||||||
|
name: _,
|
||||||
|
start: best_start,
|
||||||
|
length: _,
|
||||||
|
}| start >= best_start,
|
||||||
|
)
|
||||||
|
.unwrap_or(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_scene(self) -> Option<(String, FrameNumber, FrameNumber)> {
|
/// Return the next scene.
|
||||||
|
pub fn next_scene(self) -> Option<Scene> {
|
||||||
let current_frame = self.0.read().current_frame();
|
let current_frame = self.0.read().current_frame();
|
||||||
|
|
||||||
self.filter_scenes(|best, (_scene, frame, _end)| {
|
self.filter_scenes(
|
||||||
frame > current_frame && best.map(|v| frame <= v.1).unwrap_or(true)
|
|best,
|
||||||
})
|
Scene {
|
||||||
|
name: _,
|
||||||
|
start,
|
||||||
|
length: _,
|
||||||
|
}| {
|
||||||
|
*start > current_frame
|
||||||
|
&& best
|
||||||
|
.map(
|
||||||
|
|Scene {
|
||||||
|
name: _,
|
||||||
|
start: best_start,
|
||||||
|
length: _,
|
||||||
|
}| start <= best_start,
|
||||||
|
)
|
||||||
|
.unwrap_or(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter_scenes<F>(self, mut cond: F) -> Option<(String, FrameNumber, FrameNumber)>
|
/// Return all scenes in the movie.
|
||||||
|
///
|
||||||
|
/// Scenes will be sorted in playback order.
|
||||||
|
pub fn scenes(self) -> Vec<Scene> {
|
||||||
|
let read = self.0.read();
|
||||||
|
let mut out = Vec::new();
|
||||||
|
|
||||||
|
for (_, scene) in read.static_data.scene_labels.iter() {
|
||||||
|
out.push(scene.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
out.sort_unstable_by(
|
||||||
|
|Scene {
|
||||||
|
name: _,
|
||||||
|
start: a,
|
||||||
|
length: _,
|
||||||
|
},
|
||||||
|
Scene {
|
||||||
|
name: _,
|
||||||
|
start: b,
|
||||||
|
length: _,
|
||||||
|
}| a.cmp(b),
|
||||||
|
);
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scan through the list of scenes and yield the best one, if available,
|
||||||
|
/// according to a given criterion function.
|
||||||
|
fn filter_scenes<F>(self, mut cond: F) -> Option<Scene>
|
||||||
where
|
where
|
||||||
F: FnMut(
|
F: FnMut(Option<&Scene>, &Scene) -> bool,
|
||||||
Option<(&str, FrameNumber, FrameNumber)>,
|
|
||||||
(&str, FrameNumber, FrameNumber),
|
|
||||||
) -> bool,
|
|
||||||
{
|
{
|
||||||
let read = self.0.read();
|
let read = self.0.read();
|
||||||
let mut best: Option<(&str, FrameNumber, FrameNumber)> = None;
|
let mut best: Option<&Scene> = None;
|
||||||
|
|
||||||
for (
|
for (_, scene) in read.static_data.scene_labels.iter() {
|
||||||
_,
|
if cond(best, scene) {
|
||||||
Scene {
|
best = Some(scene);
|
||||||
name,
|
|
||||||
start,
|
|
||||||
length,
|
|
||||||
},
|
|
||||||
) in read.static_data.scene_labels.iter()
|
|
||||||
{
|
|
||||||
if cond(best, (name, *start, *length)) {
|
|
||||||
best = Some((name, *start, *length));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
best.map(|(s, fnum, len)| (s.to_string(), fnum, len))
|
best.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Yield the current frame label as a tuple of string and frame number.
|
||||||
pub fn current_label(self) -> Option<(String, FrameNumber)> {
|
pub fn current_label(self) -> Option<(String, FrameNumber)> {
|
||||||
let read = self.0.read();
|
let read = self.0.read();
|
||||||
let current_frame = read.current_frame();
|
let current_frame = read.current_frame();
|
||||||
|
@ -2863,10 +2936,20 @@ impl<'gc, 'a> MovieClip<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Scene {
|
pub struct Scene {
|
||||||
name: String,
|
pub name: String,
|
||||||
start: FrameNumber,
|
pub start: FrameNumber,
|
||||||
length: FrameNumber,
|
pub length: FrameNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Scene {
|
||||||
|
fn default() -> Self {
|
||||||
|
Scene {
|
||||||
|
name: "".to_string(),
|
||||||
|
start: 0,
|
||||||
|
length: u16::MAX,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Static data shared between all instances of a movie clip.
|
/// Static data shared between all instances of a movie clip.
|
||||||
|
|
|
@ -397,6 +397,7 @@ swf_tests! {
|
||||||
(as3_movieclip_currentlabels, "avm2/movieclip_currentlabels", 5),
|
(as3_movieclip_currentlabels, "avm2/movieclip_currentlabels", 5),
|
||||||
(as3_scene_constr, "avm2/scene_constr", 5),
|
(as3_scene_constr, "avm2/scene_constr", 5),
|
||||||
(as3_movieclip_currentscene, "avm2/movieclip_currentscene", 5),
|
(as3_movieclip_currentscene, "avm2/movieclip_currentscene", 5),
|
||||||
|
(as3_movieclip_scenes, "avm2/movieclip_scenes", 5),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.
|
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
//(contents of this.scenes)
|
||||||
|
Scene 1
|
||||||
|
3
|
||||||
|
1
|
||||||
|
frame1
|
||||||
|
3
|
||||||
|
frame3
|
||||||
|
Scene 2
|
||||||
|
1
|
||||||
|
1
|
||||||
|
frame4
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue