ruffle/core/src/movie_clip.rs

641 lines
23 KiB
Rust
Raw Normal View History

2019-04-30 08:53:21 +00:00
use crate::audio::AudioStreamHandle;
2019-04-29 05:55:44 +00:00
use crate::character::Character;
2019-04-26 21:11:29 +00:00
use crate::color_transform::ColorTransform;
2019-05-07 10:34:17 +00:00
use crate::display_object::{DisplayObject, DisplayObjectBase, DisplayObjectImpl};
2019-05-04 18:45:11 +00:00
use crate::font::Font;
2019-05-07 10:22:58 +00:00
use crate::graphic::Graphic;
2019-04-25 17:52:22 +00:00
use crate::matrix::Matrix;
use crate::morph_shape::MorphShape;
2019-04-27 01:55:06 +00:00
use crate::player::{RenderContext, UpdateContext};
use crate::prelude::*;
2019-05-04 18:45:11 +00:00
use crate::text::Text;
2019-05-09 01:10:43 +00:00
use gc::{Gc, GcCell};
2019-04-25 17:52:22 +00:00
use std::cell::RefCell;
2019-05-07 06:31:34 +00:00
use std::collections::{BTreeMap, HashMap, VecDeque};
2019-05-02 00:46:49 +00:00
use swf::read::SwfRead;
2019-04-25 17:52:22 +00:00
type Depth = i16;
type FrameNumber = u16;
2019-05-09 01:10:43 +00:00
#[derive(Clone, Trace, Finalize)]
2019-04-25 17:52:22 +00:00
pub struct MovieClip {
2019-04-29 05:55:44 +00:00
base: DisplayObjectBase,
2019-04-26 03:27:44 +00:00
tag_stream_start: Option<u64>,
tag_stream_pos: u64,
2019-04-25 17:52:22 +00:00
is_playing: bool,
2019-05-07 06:31:34 +00:00
action: Option<(usize, usize)>,
2019-05-02 00:46:49 +00:00
goto_queue: VecDeque<FrameNumber>,
2019-04-25 17:52:22 +00:00
current_frame: FrameNumber,
total_frames: FrameNumber,
2019-05-09 01:10:43 +00:00
#[unsafe_ignore_trace]
2019-04-30 08:53:21 +00:00
audio_stream: Option<AudioStreamHandle>,
stream_started: bool,
2019-05-09 01:10:43 +00:00
children: BTreeMap<Depth, Gc<GcCell<DisplayObject>>>,
2019-04-25 17:52:22 +00:00
}
impl MovieClip {
2019-04-29 05:55:44 +00:00
pub fn new() -> MovieClip {
MovieClip {
base: Default::default(),
2019-04-26 03:27:44 +00:00
tag_stream_start: None,
tag_stream_pos: 0,
2019-04-25 17:52:22 +00:00
is_playing: true,
2019-05-07 06:31:34 +00:00
action: None,
2019-05-02 00:46:49 +00:00
goto_queue: VecDeque::new(),
2019-04-26 03:27:44 +00:00
current_frame: 0,
2019-04-25 17:52:22 +00:00
total_frames: 1,
2019-04-30 08:53:21 +00:00
audio_stream: None,
stream_started: false,
2019-05-03 21:25:01 +00:00
children: BTreeMap::new(),
2019-04-29 05:55:44 +00:00
}
2019-04-25 17:52:22 +00:00
}
2019-04-26 03:27:44 +00:00
pub fn new_with_data(tag_stream_start: u64, num_frames: u16) -> MovieClip {
2019-04-25 17:52:22 +00:00
MovieClip {
2019-04-29 05:55:44 +00:00
base: Default::default(),
2019-04-26 03:27:44 +00:00
tag_stream_start: Some(tag_stream_start),
tag_stream_pos: tag_stream_start,
2019-04-25 17:52:22 +00:00
is_playing: true,
2019-05-07 06:31:34 +00:00
action: None,
2019-05-02 00:46:49 +00:00
goto_queue: VecDeque::new(),
2019-04-26 03:27:44 +00:00
current_frame: 0,
2019-04-30 08:53:21 +00:00
audio_stream: None,
stream_started: false,
2019-04-25 17:52:22 +00:00
total_frames: num_frames,
2019-05-03 21:25:01 +00:00
children: BTreeMap::new(),
2019-04-25 17:52:22 +00:00
}
}
2019-05-02 00:46:49 +00:00
pub fn playing(&self) -> bool {
self.is_playing
}
pub fn next_frame(&mut self) {
if self.current_frame + 1 <= self.total_frames {
self.goto_frame(self.current_frame + 1, true);
}
}
pub fn play(&mut self) {
self.is_playing = true;
}
pub fn prev_frame(&mut self) {
if self.current_frame > 1 {
self.goto_frame(self.current_frame - 1, true);
}
}
pub fn stop(&mut self) {
self.is_playing = false;
}
pub fn goto_frame(&mut self, frame: FrameNumber, stop: bool) {
self.goto_queue.push_back(frame);
if stop {
self.stop();
} else {
self.play();
}
}
2019-05-07 06:31:34 +00:00
pub fn x(&self) -> f32 {
self.get_matrix().tx
}
pub fn y(&self) -> f32 {
self.get_matrix().ty
}
pub fn x_scale(&self) -> f32 {
self.get_matrix().a * 100.0
}
pub fn y_scale(&self) -> f32 {
self.get_matrix().d * 100.0
}
pub fn current_frame(&self) -> FrameNumber {
self.current_frame
}
pub fn total_frames(&self) -> FrameNumber {
self.total_frames
}
pub fn frames_loaded(&self) -> FrameNumber {
// TODO(Herschel): root needs to progressively stream in frames.
self.total_frames
}
2019-05-09 01:10:43 +00:00
pub fn get_child_by_name(&self, name: &str) -> Option<&Gc<GcCell<DisplayObject>>> {
2019-05-07 06:31:34 +00:00
self.children
.values()
.find(|child| child.borrow().name() == name)
}
pub fn frame_label_to_number(
&self,
frame_label: &str,
context: &mut UpdateContext,
) -> Option<FrameNumber> {
// TODO(Herschel): We should cache the labels in the preload step.
let pos = context.tag_stream.get_ref().position();
context
.tag_stream
.get_inner()
.set_position(self.tag_stream_start.unwrap());
use swf::Tag;
let mut frame_num = 1;
while let Ok(Some(tag)) = context.tag_stream.read_tag() {
match tag {
Tag::FrameLabel { label, .. } => {
if label == frame_label {
context.tag_stream.get_inner().set_position(pos);
return Some(frame_num);
}
}
Tag::ShowFrame => frame_num += 1,
_ => (),
}
}
context.tag_stream.get_inner().set_position(pos);
None
}
pub fn action(&self) -> Option<(usize, usize)> {
self.action
}
pub fn run_goto_queue(&mut self, context: &mut UpdateContext) {
2019-05-02 00:46:49 +00:00
let mut i = 0;
while i < self.goto_queue.len() {
let frame = self.goto_queue[i];
if frame >= self.current_frame {
// Advancing
while self.current_frame + 1 < frame {
self.run_frame_internal(context, true);
}
self.run_frame_internal(context, false);
} else {
// Rewind
// Reset everything to blank, start from frame 1,
// and advance forward
self.children.clear();
self.tag_stream_pos = self.tag_stream_start.unwrap_or(0);
self.current_frame = 0;
while self.current_frame + 1 < frame {
self.run_frame_internal(context, true);
}
self.run_frame_internal(context, false);
}
i += 1;
}
self.goto_queue.clear();
}
2019-05-03 21:25:01 +00:00
pub fn place_object(&mut self, place_object: &swf::PlaceObject, context: &mut UpdateContext) {
2019-04-25 17:52:22 +00:00
use swf::PlaceObjectAction;
2019-04-28 06:08:59 +00:00
let character = match place_object.action {
2019-04-25 17:52:22 +00:00
PlaceObjectAction::Place(id) => {
// TODO(Herschel): Behavior when character doesn't exist/isn't a DisplayObject?
2019-04-26 03:27:44 +00:00
let character =
2019-04-27 01:55:06 +00:00
if let Ok(character) = context.library.instantiate_display_object(id) {
2019-05-09 01:10:43 +00:00
Gc::new(GcCell::new(character))
2019-04-26 03:27:44 +00:00
} else {
return;
};
2019-04-25 17:52:22 +00:00
// TODO(Herschel): Behavior when depth is occupied? (I think it replaces)
2019-05-03 21:25:01 +00:00
self.children.insert(place_object.depth, character.clone());
2019-04-26 03:27:44 +00:00
character
}
PlaceObjectAction::Modify => {
2019-05-03 21:25:01 +00:00
if let Some(child) = self.children.get(&place_object.depth) {
2019-04-26 03:27:44 +00:00
child.clone()
} else {
return;
}
2019-04-25 17:52:22 +00:00
}
PlaceObjectAction::Replace(id) => {
2019-04-26 03:27:44 +00:00
let character =
2019-04-27 01:55:06 +00:00
if let Ok(character) = context.library.instantiate_display_object(id) {
2019-05-09 01:10:43 +00:00
Gc::new(GcCell::new(character))
2019-04-26 03:27:44 +00:00
} else {
return;
};
2019-04-25 17:52:22 +00:00
2019-05-03 21:25:01 +00:00
if let Some(prev_character) =
self.children.insert(place_object.depth, character.clone())
2019-05-01 16:55:54 +00:00
{
character
.borrow_mut()
.set_matrix(prev_character.borrow().get_matrix());
character
.borrow_mut()
.set_color_transform(prev_character.borrow().get_color_transform());
}
2019-04-26 03:27:44 +00:00
character
2019-04-25 17:52:22 +00:00
}
2019-04-26 03:27:44 +00:00
};
let mut character = character.borrow_mut();
if let Some(matrix) = &place_object.matrix {
let m = matrix.clone();
2019-04-29 05:55:44 +00:00
character.set_matrix(&Matrix::from(m));
2019-04-25 17:52:22 +00:00
}
2019-05-01 16:55:54 +00:00
if let Some(color_transform) = &place_object.color_transform {
character.set_color_transform(&ColorTransform::from(color_transform.clone()));
}
if let Some(name) = &place_object.name {
character.set_name(name);
}
if let Some(ratio) = &place_object.ratio {
if let Some(morph_shape) = character.as_morph_shape_mut() {
morph_shape.set_ratio(*ratio);
}
}
2019-04-25 17:52:22 +00:00
}
2019-04-30 08:53:21 +00:00
2019-05-02 00:46:49 +00:00
fn run_frame_internal(&mut self, context: &mut UpdateContext, only_display_actions: bool) {
use swf::Tag;
// Advance frame number.
if self.current_frame < self.total_frames {
self.current_frame += 1;
} else {
self.current_frame = 1;
self.children.clear();
if let Some(start) = self.tag_stream_start {
self.tag_stream_pos = start;
}
2019-04-30 08:53:21 +00:00
}
2019-04-25 17:52:22 +00:00
2019-05-02 00:46:49 +00:00
context
.tag_stream
.get_inner()
.set_position(self.tag_stream_pos);
let mut start_pos = self.tag_stream_pos;
2019-04-26 03:27:44 +00:00
2019-05-02 00:46:49 +00:00
while let Ok(Some(tag)) = context.tag_stream.read_tag() {
if only_display_actions {
match tag {
Tag::ShowFrame => break,
2019-05-03 21:25:01 +00:00
Tag::PlaceObject(place_object) => self.place_object(&*place_object, context),
2019-05-02 00:46:49 +00:00
Tag::RemoveObject { depth, .. } => {
// TODO(Herschel): How does the character ID work for RemoveObject?
self.children.remove(&depth);
}
// All non-display-list tags get ignored.
_ => (),
}
} else {
2019-04-25 17:52:22 +00:00
match tag {
// Definition Tags
2019-04-29 05:55:44 +00:00
Tag::SetBackgroundColor(color) => *context.background_color = color,
// Control Tags
2019-04-25 17:52:22 +00:00
Tag::ShowFrame => break,
2019-05-03 21:25:01 +00:00
Tag::PlaceObject(place_object) => self.place_object(&*place_object, context),
2019-04-27 01:55:06 +00:00
Tag::RemoveObject { depth, .. } => {
2019-04-26 03:27:44 +00:00
// TODO(Herschel): How does the character ID work for RemoveObject?
self.children.remove(&depth);
2019-04-25 17:52:22 +00:00
}
2019-04-26 03:27:44 +00:00
2019-05-05 22:55:27 +00:00
Tag::StartSound { id, sound_info } => {
if let Some(handle) = context.library.get_sound(id) {
context.audio.play_sound(handle);
}
}
2019-04-30 08:53:21 +00:00
Tag::SoundStreamHead(info) => self.sound_stream_head(&info, context, 0, 1),
Tag::SoundStreamHead2(info) => self.sound_stream_head(&info, context, 0, 2),
Tag::SoundStreamBlock(samples) => {
self.sound_stream_block(&samples[..], context, 0)
}
2019-04-26 03:27:44 +00:00
Tag::JpegTables(_) => (),
2019-05-07 06:31:34 +00:00
// Tag::DoAction(data) => self.do_action(context, &data[..]),
Tag::DoAction(data) => {
let pos = context.tag_stream.get_ref().position();
context.tag_stream.get_inner().set_position(start_pos);
context.tag_stream.read_tag_code_and_length().unwrap();
let start_pos = context.tag_stream.get_ref().position();
context.tag_stream.get_inner().set_position(pos);
self.action = Some((start_pos as usize, data.len()));
}
2019-05-07 16:52:22 +00:00
_ => (), // info!("Umimplemented tag: {:?}", tag),
2019-04-25 17:52:22 +00:00
}
2019-04-29 05:55:44 +00:00
start_pos = context.tag_stream.get_ref().position();
2019-04-25 17:52:22 +00:00
}
2019-05-02 00:46:49 +00:00
}
self.tag_stream_pos = context.tag_stream.get_ref().position();
}
fn sound_stream_head(
&mut self,
stream_info: &swf::SoundStreamInfo,
context: &mut UpdateContext,
_length: usize,
_version: u8,
) {
if let Some(stream) = self.audio_stream {
//self.audio_stream = Some(context.audio.register_stream(stream_info));
self.stream_started = false;
}
}
fn sound_stream_block(&mut self, samples: &[u8], context: &mut UpdateContext, _length: usize) {
if let Some(stream) = self.audio_stream {
if !self.stream_started {
self.stream_started = context.audio.start_stream(stream);
}
context.audio.queue_stream_samples(stream, samples);
}
}
fn preload_sound_stream_head(
&mut self,
stream_info: &swf::SoundStreamInfo,
context: &mut UpdateContext,
_length: usize,
_version: u8,
2019-05-02 00:46:49 +00:00
) {
if self.audio_stream.is_none() {
self.audio_stream = Some(context.audio.register_stream(stream_info));
}
}
fn preload_sound_stream_block(
&mut self,
samples: &[u8],
context: &mut UpdateContext,
_length: usize,
) {
2019-05-02 00:46:49 +00:00
if let Some(stream) = self.audio_stream {
context.audio.preload_stream_samples(stream, samples)
2019-05-02 00:46:49 +00:00
}
}
}
2019-05-07 10:34:17 +00:00
impl DisplayObjectImpl for MovieClip {
impl_display_object!(base);
fn preload(&mut self, context: &mut UpdateContext) {
context
.tag_stream
.get_inner()
.set_position(self.tag_stream_start.unwrap());
let mut ids = HashMap::new();
use swf::Tag;
2019-05-07 10:22:58 +00:00
let mut start_pos = context.tag_stream.get_ref().position();
while let Ok(Some(tag)) = context.tag_stream.read_tag() {
match tag {
// Definition Tags
2019-05-07 10:22:58 +00:00
Tag::DefineButton2(swf_button) => {
if !context.library.contains_character(swf_button.id) {
let button =
crate::button::Button::from_swf_tag(&swf_button, &context.library);
context
.library
2019-05-07 10:22:58 +00:00
.register_character(swf_button.id, Character::Button(Box::new(button)));
}
}
Tag::DefineBits { id, jpeg_data } => {
if !context.library.contains_character(id) {
let handle = context.renderer.register_bitmap_jpeg(
id,
&jpeg_data,
context.library.jpeg_tables().unwrap(),
);
context
.library
.register_character(id, Character::Bitmap(handle));
}
}
Tag::DefineBitsJpeg2 { id, jpeg_data } => {
if !context.library.contains_character(id) {
let handle = context.renderer.register_bitmap_jpeg_2(id, &jpeg_data);
context
.library
.register_character(id, Character::Bitmap(handle));
}
}
Tag::DefineBitsLossless(bitmap) => {
if !context.library.contains_character(bitmap.id) {
let handle = context.renderer.register_bitmap_png(&bitmap);
context
.library
.register_character(bitmap.id, Character::Bitmap(handle));
}
}
2019-05-09 21:14:21 +00:00
Tag::DefineFont(font) => {
if !context.library.contains_character(font.id) {
let glyphs = font
.glyphs
.into_iter()
.map(|g| swf::Glyph {
shape_records: g,
code: 0,
advance: None,
bounds: None,
})
.collect::<Vec<_>>();
let font = swf::Font {
id: font.id,
version: 0,
name: "".to_string(),
glyphs,
language: swf::Language::Unknown,
layout: None,
is_small_text: false,
is_shift_jis: false,
is_ansi: false,
is_bold: false,
is_italic: false,
};
let font_object = Font::from_swf_tag(context, &font).unwrap();
context
.library
.register_character(font.id, Character::Font(Box::new(font_object)));
}
}
Tag::DefineFont2(font) => {
if !context.library.contains_character(font.id) {
let font_object = Font::from_swf_tag(context, &font).unwrap();
context
.library
.register_character(font.id, Character::Font(Box::new(font_object)));
}
}
2019-05-09 21:14:21 +00:00
Tag::DefineFontInfo(info) => {
// TODO(Herschel)
}
Tag::DefineMorphShape(swf_shape) => {
if !context.library.contains_character(swf_shape.id) {
let morph_shape = MorphShape::from_swf_tag(&swf_shape, context);
context.library.register_character(
swf_shape.id,
Character::MorphShape(Box::new(morph_shape)),
);
}
}
2019-05-07 10:22:58 +00:00
Tag::DefineShape(swf_shape) => {
if !context.library.contains_character(swf_shape.id) {
let graphic = Graphic::from_swf_tag(&swf_shape, context);
context.library.register_character(
2019-05-07 10:22:58 +00:00
swf_shape.id,
Character::Graphic(Box::new(graphic)),
);
}
}
Tag::DefineSound(sound) => {
if !context.library.contains_character(sound.id) {
let handle = context.audio.register_sound(&sound).unwrap();
context
.library
.register_character(sound.id, Character::Sound(handle));
}
}
2019-05-07 10:22:58 +00:00
Tag::DefineSprite(swf_sprite) => {
let pos = context.tag_stream.get_ref().position();
context.tag_stream.get_inner().set_position(start_pos);
context.tag_stream.read_tag_code_and_length().unwrap();
context.tag_stream.read_u32().unwrap();
let mc_start_pos = context.tag_stream.get_ref().position();
context.tag_stream.get_inner().set_position(pos);
if !context.library.contains_character(swf_sprite.id) {
let mut movie_clip =
2019-05-07 10:22:58 +00:00
MovieClip::new_with_data(mc_start_pos, swf_sprite.num_frames);
movie_clip.preload(context);
2019-05-07 10:22:58 +00:00
context.library.register_character(
swf_sprite.id,
Character::MovieClip(Box::new(movie_clip)),
);
}
}
Tag::DefineText(text) => {
if !context.library.contains_character(text.id) {
let text_object = Text::from_swf_tag(&text);
context
.library
.register_character(text.id, Character::Text(Box::new(text_object)));
}
}
Tag::PlaceObject(place_object) => {
use swf::PlaceObjectAction;
match place_object.action {
PlaceObjectAction::Place(id) | PlaceObjectAction::Replace(id) => {
ids.insert(place_object.depth, id);
}
_ => (),
}
if let Some(ratio) = place_object.ratio {
if let Some(&id) = ids.get(&place_object.depth) {
if let Some(Character::MorphShape(morph_shape)) =
context.library.get_character_mut(id)
{
morph_shape.register_ratio(context.renderer, ratio);
}
}
}
}
Tag::SoundStreamHead(info) => self.preload_sound_stream_head(&info, context, 0, 1),
Tag::SoundStreamHead2(info) => self.preload_sound_stream_head(&info, context, 0, 2),
Tag::SoundStreamBlock(samples) => {
self.preload_sound_stream_block(&samples[..], context, 0)
}
Tag::JpegTables(data) => context.library.set_jpeg_tables(data),
_ => (),
}
2019-05-07 10:22:58 +00:00
start_pos = context.tag_stream.get_inner().position();
}
if let Some(stream) = self.audio_stream {
context.audio.preload_stream_finalize(stream);
}
}
2019-05-02 00:46:49 +00:00
fn run_frame(&mut self, context: &mut UpdateContext) {
2019-05-07 06:31:34 +00:00
self.action = None;
2019-05-03 18:44:12 +00:00
if self.tag_stream_start.is_some() {
2019-05-02 00:46:49 +00:00
context
.position_stack
.push(context.tag_stream.get_ref().position());
2019-05-03 18:44:12 +00:00
}
2019-05-02 00:46:49 +00:00
2019-05-03 21:25:01 +00:00
if self.is_playing {
self.run_frame_internal(context, false);
}
2019-05-02 00:46:49 +00:00
2019-05-03 21:25:01 +00:00
// TODO(Herschel): Verify order of execution for parent/children.
// Parent first? Children first? Sorted by depth?
for child in self.children.values() {
child.borrow_mut().run_frame(context);
}
2019-05-02 00:46:49 +00:00
2019-05-03 18:44:12 +00:00
if self.tag_stream_start.is_some() {
2019-04-26 03:27:44 +00:00
context
.tag_stream
.get_inner()
.set_position(context.position_stack.pop().unwrap());
}
}
2019-05-02 00:46:49 +00:00
fn run_post_frame(&mut self, context: &mut UpdateContext) {
self.run_goto_queue(context);
2019-05-07 06:31:34 +00:00
//for child in self.children.values() {
//child.borrow_mut().run_post_frame(context);
//}
2019-04-25 17:52:22 +00:00
}
fn render(&self, context: &mut RenderContext) {
2019-05-01 16:55:54 +00:00
context.transform_stack.push(self.transform());
2019-04-25 17:52:22 +00:00
2019-05-03 21:25:01 +00:00
for child in self.children.values() {
child.borrow_mut().render(context);
2019-04-25 17:52:22 +00:00
}
2019-05-01 16:55:54 +00:00
context.transform_stack.pop();
2019-04-25 17:52:22 +00:00
}
2019-05-03 18:44:12 +00:00
fn handle_click(&mut self, pos: (f32, f32)) {
for child in self.children.values_mut() {
child.borrow_mut().handle_click(pos);
}
}
2019-05-07 06:31:34 +00:00
2019-05-09 01:10:43 +00:00
fn visit_children(&self, queue: &mut VecDeque<Gc<GcCell<DisplayObject>>>) {
2019-05-07 06:31:34 +00:00
for child in self.children.values() {
queue.push_back(child.clone());
}
}
fn as_movie_clip(&self) -> Option<&crate::movie_clip::MovieClip> {
Some(self)
}
fn as_movie_clip_mut(&mut self) -> Option<&mut crate::movie_clip::MovieClip> {
Some(self)
}
2019-04-25 17:52:22 +00:00
}