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::value::Value;
|
||||
use crate::avm2::Error;
|
||||
use crate::display_object::{MovieClip, TDisplayObject};
|
||||
use crate::display_object::{MovieClip, Scene, TDisplayObject};
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
||||
/// 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(|dobj| dobj.as_movie_clip())
|
||||
{
|
||||
if let Some((_scene, scene_basis, _length)) = mc.current_scene() {
|
||||
return Ok(((mc.current_frame() + 1) - scene_basis).into());
|
||||
if let Some(Scene {
|
||||
name: _,
|
||||
start,
|
||||
length: _,
|
||||
}) = mc.current_scene()
|
||||
{
|
||||
return Ok(((mc.current_frame() + 1) - start).into());
|
||||
} else {
|
||||
return Ok(mc.current_frame().into());
|
||||
}
|
||||
|
@ -122,16 +127,24 @@ pub fn current_label<'gc>(
|
|||
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>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
mc: MovieClip<'gc>,
|
||||
scene: &Scene,
|
||||
) -> Result<(String, u16, Object<'gc>), Error> {
|
||||
let (scene_name, scene_start, scene_length) =
|
||||
mc.current_scene().unwrap_or(("".to_string(), 0, u16::MAX));
|
||||
let Scene {
|
||||
name: scene_name,
|
||||
start: scene_start,
|
||||
length: scene_length,
|
||||
} = scene;
|
||||
let mut frame_labels = Vec::new();
|
||||
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 local_frame = frame - scene_start + 1;
|
||||
let args = [name, local_frame.into()];
|
||||
|
@ -143,8 +156,8 @@ fn labels_for_scene<'gc>(
|
|||
}
|
||||
|
||||
Ok((
|
||||
scene_name,
|
||||
scene_length,
|
||||
scene_name.to_string(),
|
||||
*scene_length,
|
||||
ArrayObject::from_array(
|
||||
ArrayStorage::from_storage(frame_labels),
|
||||
activation.context.avm2.prototypes().array,
|
||||
|
@ -163,7 +176,8 @@ pub fn current_labels<'gc>(
|
|||
.and_then(|o| o.as_display_object())
|
||||
.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)
|
||||
|
@ -179,7 +193,8 @@ pub fn current_scene<'gc>(
|
|||
.and_then(|o| o.as_display_object())
|
||||
.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 args = [
|
||||
AvmString::new(activation.context.gc_context, scene_name).into(),
|
||||
|
@ -197,6 +212,46 @@ pub fn current_scene<'gc>(
|
|||
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`.
|
||||
pub fn frames_loaded<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
|
@ -398,8 +453,13 @@ pub fn prev_scene<'gc>(
|
|||
.and_then(|o| o.as_display_object())
|
||||
.and_then(|dobj| dobj.as_movie_clip())
|
||||
{
|
||||
if let Some((_scene, target_frame, _length)) = mc.previous_scene() {
|
||||
mc.goto_frame(&mut activation.context, target_frame, false);
|
||||
if let Some(Scene {
|
||||
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(|dobj| dobj.as_movie_clip())
|
||||
{
|
||||
if let Some((_scene, target_frame, _length)) = mc.next_scene() {
|
||||
mc.goto_frame(&mut activation.context, target_frame, false);
|
||||
if let Some(Scene {
|
||||
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),
|
||||
));
|
||||
|
||||
write.define_instance_trait(Trait::from_getter(
|
||||
QName::new(Namespace::package(""), "scenes"),
|
||||
Method::from_builtin(scenes),
|
||||
));
|
||||
|
||||
write.define_instance_trait(Trait::from_getter(
|
||||
QName::new(Namespace::package(""), "framesLoaded"),
|
||||
Method::from_builtin(frames_loaded),
|
||||
|
|
|
@ -30,7 +30,7 @@ pub use button::Button;
|
|||
pub use edit_text::{AutoSizeMode, EditText};
|
||||
pub use graphic::Graphic;
|
||||
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
||||
pub use movie_clip::MovieClip;
|
||||
pub use movie_clip::{MovieClip, Scene};
|
||||
pub use text::Text;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
@ -554,9 +554,6 @@ impl<'gc> MovieClip<'gc> {
|
|||
static_data: &mut MovieClipStatic,
|
||||
) -> DecodeResult {
|
||||
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
|
||||
.scenes
|
||||
.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
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
self.filter_scenes(|best, (_scene, frame, _end)| {
|
||||
frame <= current_frame && best.map(|v| frame >= v.1).unwrap_or(true)
|
||||
})
|
||||
self.filter_scenes(
|
||||
|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
|
||||
.current_scene()
|
||||
.map(|v| v.1)
|
||||
.map(
|
||||
|Scene {
|
||||
name: _,
|
||||
start,
|
||||
length: _,
|
||||
}| start,
|
||||
)
|
||||
.unwrap_or_else(|| self.current_frame());
|
||||
|
||||
self.filter_scenes(|best, (_scene, frame, _end)| {
|
||||
frame < current_frame && best.map(|v| frame >= v.1).unwrap_or(true)
|
||||
})
|
||||
self.filter_scenes(
|
||||
|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();
|
||||
|
||||
self.filter_scenes(|best, (_scene, frame, _end)| {
|
||||
frame > current_frame && best.map(|v| frame <= v.1).unwrap_or(true)
|
||||
})
|
||||
self.filter_scenes(
|
||||
|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
|
||||
F: FnMut(
|
||||
Option<(&str, FrameNumber, FrameNumber)>,
|
||||
(&str, FrameNumber, FrameNumber),
|
||||
) -> bool,
|
||||
F: FnMut(Option<&Scene>, &Scene) -> bool,
|
||||
{
|
||||
let read = self.0.read();
|
||||
let mut best: Option<(&str, FrameNumber, FrameNumber)> = None;
|
||||
let mut best: Option<&Scene> = None;
|
||||
|
||||
for (
|
||||
_,
|
||||
Scene {
|
||||
name,
|
||||
start,
|
||||
length,
|
||||
},
|
||||
) in read.static_data.scene_labels.iter()
|
||||
{
|
||||
if cond(best, (name, *start, *length)) {
|
||||
best = Some((name, *start, *length));
|
||||
for (_, scene) in read.static_data.scene_labels.iter() {
|
||||
if cond(best, scene) {
|
||||
best = Some(scene);
|
||||
}
|
||||
}
|
||||
|
||||
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)> {
|
||||
let read = self.0.read();
|
||||
let current_frame = read.current_frame();
|
||||
|
@ -2863,10 +2936,20 @@ impl<'gc, 'a> MovieClip<'gc> {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Scene {
|
||||
name: String,
|
||||
start: FrameNumber,
|
||||
length: FrameNumber,
|
||||
pub struct Scene {
|
||||
pub name: String,
|
||||
pub start: 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.
|
||||
|
|
|
@ -397,6 +397,7 @@ swf_tests! {
|
|||
(as3_movieclip_currentlabels, "avm2/movieclip_currentlabels", 5),
|
||||
(as3_scene_constr, "avm2/scene_constr", 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.
|
||||
|
|
|
@ -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