2023-02-09 19:06:17 +00:00
|
|
|
use crate::avm1::globals::system::SandboxType;
|
2022-08-19 14:14:37 +00:00
|
|
|
use crate::avm1::Attribute;
|
|
|
|
use crate::avm1::Avm1;
|
|
|
|
use crate::avm1::Object;
|
|
|
|
use crate::avm1::SystemProperties;
|
|
|
|
use crate::avm1::VariableDumper;
|
|
|
|
use crate::avm1::{Activation, ActivationIdentifier};
|
2022-08-19 13:11:05 +00:00
|
|
|
use crate::avm1::{ScriptObject, TObject, Value};
|
2022-07-27 14:44:42 +00:00
|
|
|
use crate::avm2::{
|
2022-08-19 13:11:05 +00:00
|
|
|
object::LoaderInfoObject, object::TObject as _, Activation as Avm2Activation, Avm2, CallStack,
|
2023-03-26 21:11:36 +00:00
|
|
|
Domain as Avm2Domain, Object as Avm2Object,
|
2022-07-27 14:44:42 +00:00
|
|
|
};
|
2021-01-23 02:03:59 +00:00
|
|
|
use crate::backend::{
|
|
|
|
audio::{AudioBackend, AudioManager},
|
|
|
|
log::LogBackend,
|
2022-06-11 08:55:17 +00:00
|
|
|
navigator::{NavigatorBackend, Request},
|
2021-01-31 00:36:45 +00:00
|
|
|
storage::StorageBackend,
|
2021-11-25 22:56:24 +00:00
|
|
|
ui::{InputManager, MouseCursor, UiBackend},
|
2021-01-23 02:03:59 +00:00
|
|
|
};
|
2023-02-28 16:14:54 +00:00
|
|
|
use crate::compatibility_rules::CompatibilityRules;
|
2021-01-06 09:08:47 +00:00
|
|
|
use crate::config::Letterbox;
|
2023-03-15 22:46:43 +00:00
|
|
|
use crate::context::GcContext;
|
2020-04-25 09:05:01 +00:00
|
|
|
use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext};
|
2022-09-14 20:29:25 +00:00
|
|
|
use crate::context_menu::{
|
|
|
|
BuiltInItemFlags, ContextMenuCallback, ContextMenuItem, ContextMenuState,
|
|
|
|
};
|
2023-02-17 19:04:52 +00:00
|
|
|
use crate::display_object::Avm2MousePick;
|
2021-09-02 13:14:11 +00:00
|
|
|
use crate::display_object::{
|
2023-02-03 14:03:28 +00:00
|
|
|
EditText, InteractiveObject, MovieClip, Stage, StageAlign, StageDisplayState, StageScaleMode,
|
|
|
|
TInteractiveObject, WindowMode,
|
2021-09-02 13:14:11 +00:00
|
|
|
};
|
2021-12-17 10:14:39 +00:00
|
|
|
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, MouseButton, PlayerEvent};
|
2020-08-28 15:24:26 +00:00
|
|
|
use crate::external::Value as ExternalValue;
|
2020-08-27 21:27:54 +00:00
|
|
|
use crate::external::{ExternalInterface, ExternalInterfaceProvider};
|
2020-10-29 23:09:57 +00:00
|
|
|
use crate::focus_tracker::FocusTracker;
|
2022-08-25 22:36:15 +00:00
|
|
|
use crate::font::Font;
|
2022-08-28 19:22:22 +00:00
|
|
|
use crate::frame_lifecycle::{run_all_phases_avm2, FramePhase};
|
2019-08-26 23:38:37 +00:00
|
|
|
use crate::library::Library;
|
2021-07-31 23:38:44 +00:00
|
|
|
use crate::limits::ExecutionLimit;
|
2022-09-17 23:11:07 +00:00
|
|
|
use crate::loader::{LoadBehavior, LoadManager};
|
2022-08-11 14:55:19 +00:00
|
|
|
use crate::locale::get_current_date_time;
|
2019-08-26 23:38:37 +00:00
|
|
|
use crate::prelude::*;
|
2023-03-18 02:25:54 +00:00
|
|
|
use crate::streams::StreamManager;
|
2023-03-15 22:46:43 +00:00
|
|
|
use crate::string::{AvmString, AvmStringInterner};
|
2023-01-31 15:45:14 +00:00
|
|
|
use crate::stub::StubCollection;
|
2020-06-30 19:57:51 +00:00
|
|
|
use crate::tag_utils::SwfMovie;
|
2022-05-30 21:21:17 +00:00
|
|
|
use crate::timer::Timers;
|
2022-08-12 12:57:11 +00:00
|
|
|
use crate::vminterface::Instantiator;
|
2023-01-04 00:06:57 +00:00
|
|
|
use gc_arena::{ArenaParameters, Collect, GcCell};
|
2023-05-31 21:13:44 +00:00
|
|
|
use gc_arena::{MutationContext, Rootable};
|
2020-10-11 19:10:27 +00:00
|
|
|
use instant::Instant;
|
2019-09-17 03:37:11 +00:00
|
|
|
use rand::{rngs::SmallRng, SeedableRng};
|
2022-08-04 05:50:18 +00:00
|
|
|
use ruffle_render::backend::{null::NullRenderer, RenderBackend, ViewportDimensions};
|
2022-09-03 17:22:57 +00:00
|
|
|
use ruffle_render::commands::CommandList;
|
2023-02-03 14:03:28 +00:00
|
|
|
use ruffle_render::quality::StageQuality;
|
2022-08-13 23:08:25 +00:00
|
|
|
use ruffle_render::transform::TransformStack;
|
2022-08-25 22:19:28 +00:00
|
|
|
use ruffle_video::backend::VideoBackend;
|
2022-08-28 16:30:20 +00:00
|
|
|
use std::cell::RefCell;
|
2021-04-15 03:29:12 +00:00
|
|
|
use std::collections::{HashMap, VecDeque};
|
2019-11-08 20:09:57 +00:00
|
|
|
use std::ops::DerefMut;
|
2022-08-28 16:30:20 +00:00
|
|
|
use std::rc::{Rc, Weak as RcWeak};
|
2021-09-02 13:14:11 +00:00
|
|
|
use std::str::FromStr;
|
2019-11-08 20:09:57 +00:00
|
|
|
use std::sync::{Arc, Mutex, Weak};
|
2020-10-11 19:10:27 +00:00
|
|
|
use std::time::Duration;
|
2023-01-04 10:29:46 +00:00
|
|
|
use tracing::{info, instrument};
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2019-10-09 00:07:00 +00:00
|
|
|
/// The newest known Flash Player version, serves as a default to
|
|
|
|
/// `player_version`.
|
2019-10-09 23:46:15 +00:00
|
|
|
pub const NEWEST_PLAYER_VERSION: u8 = 32;
|
2019-10-09 00:07:00 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
#[derive(Collect)]
|
2019-11-20 20:56:57 +00:00
|
|
|
#[collect(no_drop)]
|
2022-08-28 16:30:20 +00:00
|
|
|
struct GcRoot<'gc> {
|
|
|
|
callstack: GcCell<'gc, GcCallstack<'gc>>,
|
|
|
|
data: GcCell<'gc, GcRootData<'gc>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Collect, Default)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
struct GcCallstack<'gc> {
|
|
|
|
avm2: Option<GcCell<'gc, CallStack<'gc>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct StaticCallstack {
|
|
|
|
arena: RcWeak<RefCell<GcArena>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl StaticCallstack {
|
|
|
|
pub fn avm2(&self, f: impl for<'gc> FnOnce(&CallStack<'gc>)) {
|
|
|
|
if let Some(arena) = self.arena.upgrade() {
|
|
|
|
if let Ok(arena) = arena.try_borrow() {
|
|
|
|
arena.mutate(|_, root| {
|
|
|
|
let callstack = root.callstack.read();
|
|
|
|
if let Some(callstack) = callstack.avm2 {
|
2023-01-18 20:06:31 +00:00
|
|
|
let stack = callstack.read();
|
|
|
|
if !stack.is_empty() {
|
|
|
|
f(&stack)
|
|
|
|
}
|
2022-08-28 16:30:20 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-12-07 02:29:36 +00:00
|
|
|
|
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
struct GcRootData<'gc> {
|
|
|
|
library: Library<'gc>,
|
2019-12-02 02:00:01 +00:00
|
|
|
|
2021-04-15 03:29:12 +00:00
|
|
|
/// The root of the display object hierarchy.
|
2019-12-02 02:00:01 +00:00
|
|
|
///
|
2021-04-15 03:29:12 +00:00
|
|
|
/// It's children are the `level`s of AVM1, it may also be directly
|
|
|
|
/// accessed in AVM2.
|
|
|
|
stage: Stage<'gc>,
|
2020-02-24 07:41:55 +00:00
|
|
|
|
2021-06-15 18:27:55 +00:00
|
|
|
/// The display object that the mouse is currently hovering over.
|
2021-12-08 02:40:35 +00:00
|
|
|
mouse_hovered_object: Option<InteractiveObject<'gc>>,
|
2021-06-15 18:27:55 +00:00
|
|
|
|
|
|
|
/// If the mouse is down, the display object that the mouse is currently pressing.
|
2021-12-08 02:40:35 +00:00
|
|
|
mouse_pressed_object: Option<InteractiveObject<'gc>>,
|
2019-12-21 23:37:27 +00:00
|
|
|
|
|
|
|
/// The object being dragged via a `startDrag` action.
|
|
|
|
drag_object: Option<DragObject<'gc>>,
|
|
|
|
|
2020-02-04 02:22:44 +00:00
|
|
|
/// Interpreter state for AVM1 code.
|
|
|
|
avm1: Avm1<'gc>,
|
|
|
|
|
|
|
|
/// Interpreter state for AVM2 code.
|
|
|
|
avm2: Avm2<'gc>,
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
action_queue: ActionQueue<'gc>,
|
2023-03-15 22:46:43 +00:00
|
|
|
interner: AvmStringInterner<'gc>,
|
2020-01-10 23:28:49 +00:00
|
|
|
|
|
|
|
/// Object which manages asynchronous processes that need to interact with
|
|
|
|
/// data in the GC arena.
|
|
|
|
load_manager: LoadManager<'gc>,
|
2020-06-22 00:59:38 +00:00
|
|
|
|
2022-08-28 20:45:10 +00:00
|
|
|
avm1_shared_objects: HashMap<String, Object<'gc>>,
|
|
|
|
|
|
|
|
avm2_shared_objects: HashMap<String, Avm2Object<'gc>>,
|
2020-06-23 04:07:27 +00:00
|
|
|
|
|
|
|
/// Text fields with unbound variable bindings.
|
|
|
|
unbound_text_fields: Vec<EditText<'gc>>,
|
2020-07-08 02:06:19 +00:00
|
|
|
|
|
|
|
/// Timed callbacks created with `setInterval`/`setTimeout`.
|
|
|
|
timers: Timers<'gc>,
|
2020-08-27 22:16:53 +00:00
|
|
|
|
2021-05-02 22:28:00 +00:00
|
|
|
current_context_menu: Option<ContextMenuState<'gc>>,
|
|
|
|
|
2020-11-11 09:55:46 +00:00
|
|
|
/// External interface for (for example) JavaScript <-> ActionScript interaction
|
2020-08-27 22:16:53 +00:00
|
|
|
external_interface: ExternalInterface<'gc>,
|
2020-10-29 23:09:57 +00:00
|
|
|
|
|
|
|
/// A tracker for the current keyboard focused element
|
|
|
|
focus_tracker: FocusTracker<'gc>,
|
2021-01-23 02:03:59 +00:00
|
|
|
|
|
|
|
/// Manager of active sound instances.
|
|
|
|
audio_manager: AudioManager<'gc>,
|
2023-03-18 02:25:54 +00:00
|
|
|
|
|
|
|
/// List of actively playing streams to decode.
|
|
|
|
stream_manager: StreamManager<'gc>,
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
impl<'gc> GcRootData<'gc> {
|
2019-12-08 18:49:23 +00:00
|
|
|
/// Splits out parameters for creating an `UpdateContext`
|
|
|
|
/// (because we can borrow fields of `self` independently)
|
2020-06-22 00:59:38 +00:00
|
|
|
#[allow(clippy::type_complexity)]
|
2019-12-08 18:49:23 +00:00
|
|
|
fn update_context_params(
|
2019-12-07 02:29:36 +00:00
|
|
|
&mut self,
|
|
|
|
) -> (
|
2021-04-15 03:29:12 +00:00
|
|
|
Stage<'gc>,
|
2019-12-07 02:29:36 +00:00
|
|
|
&mut Library<'gc>,
|
|
|
|
&mut ActionQueue<'gc>,
|
2023-03-15 22:46:43 +00:00
|
|
|
&mut AvmStringInterner<'gc>,
|
2019-12-07 02:29:36 +00:00
|
|
|
&mut Avm1<'gc>,
|
2020-02-04 02:22:44 +00:00
|
|
|
&mut Avm2<'gc>,
|
2019-12-21 23:37:27 +00:00
|
|
|
&mut Option<DragObject<'gc>>,
|
2020-01-10 23:28:49 +00:00
|
|
|
&mut LoadManager<'gc>,
|
2020-06-22 00:59:38 +00:00
|
|
|
&mut HashMap<String, Object<'gc>>,
|
2022-08-28 20:45:10 +00:00
|
|
|
&mut HashMap<String, Avm2Object<'gc>>,
|
2020-06-23 04:07:27 +00:00
|
|
|
&mut Vec<EditText<'gc>>,
|
2020-07-08 02:06:19 +00:00
|
|
|
&mut Timers<'gc>,
|
2021-05-02 22:28:00 +00:00
|
|
|
&mut Option<ContextMenuState<'gc>>,
|
2020-08-27 22:16:53 +00:00
|
|
|
&mut ExternalInterface<'gc>,
|
2021-01-23 02:03:59 +00:00
|
|
|
&mut AudioManager<'gc>,
|
2023-03-18 02:25:54 +00:00
|
|
|
&mut StreamManager<'gc>,
|
2019-12-07 02:29:36 +00:00
|
|
|
) {
|
|
|
|
(
|
2021-04-15 03:29:12 +00:00
|
|
|
self.stage,
|
2019-12-07 02:29:36 +00:00
|
|
|
&mut self.library,
|
|
|
|
&mut self.action_queue,
|
2023-03-15 22:46:43 +00:00
|
|
|
&mut self.interner,
|
2020-02-04 02:22:44 +00:00
|
|
|
&mut self.avm1,
|
|
|
|
&mut self.avm2,
|
2019-12-21 23:37:27 +00:00
|
|
|
&mut self.drag_object,
|
2020-01-10 23:28:49 +00:00
|
|
|
&mut self.load_manager,
|
2022-08-28 20:45:10 +00:00
|
|
|
&mut self.avm1_shared_objects,
|
|
|
|
&mut self.avm2_shared_objects,
|
2020-06-23 04:07:27 +00:00
|
|
|
&mut self.unbound_text_fields,
|
2020-07-08 02:06:19 +00:00
|
|
|
&mut self.timers,
|
2021-05-02 22:28:00 +00:00
|
|
|
&mut self.current_context_menu,
|
2020-08-27 22:16:53 +00:00
|
|
|
&mut self.external_interface,
|
2021-01-23 02:03:59 +00:00
|
|
|
&mut self.audio_manager,
|
2023-03-18 02:25:54 +00:00
|
|
|
&mut self.stream_manager,
|
2019-12-07 02:29:36 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2019-10-08 04:02:09 +00:00
|
|
|
|
2023-05-31 21:13:44 +00:00
|
|
|
type GcArena = gc_arena::Arena<Rootable![GcRoot<'gc>]>;
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2019-11-08 20:09:57 +00:00
|
|
|
type Audio = Box<dyn AudioBackend>;
|
|
|
|
type Navigator = Box<dyn NavigatorBackend>;
|
|
|
|
type Renderer = Box<dyn RenderBackend>;
|
2020-06-12 18:27:20 +00:00
|
|
|
type Storage = Box<dyn StorageBackend>;
|
2020-09-05 16:19:03 +00:00
|
|
|
type Log = Box<dyn LogBackend>;
|
2021-02-12 13:03:17 +00:00
|
|
|
type Ui = Box<dyn UiBackend>;
|
2020-10-15 03:01:48 +00:00
|
|
|
type Video = Box<dyn VideoBackend>;
|
2019-11-08 20:09:57 +00:00
|
|
|
|
|
|
|
pub struct Player {
|
2019-10-09 00:07:00 +00:00
|
|
|
/// The version of the player we're emulating.
|
|
|
|
///
|
|
|
|
/// This serves a few purposes, primarily for compatibility:
|
|
|
|
///
|
|
|
|
/// * ActionScript can query the player version, ostensibly for graceful
|
|
|
|
/// degradation on older platforms. Certain SWF files broke with the
|
|
|
|
/// release of Flash Player 10 because the version string contains two
|
|
|
|
/// digits. This allows the user to play those old files.
|
|
|
|
/// * Player-specific behavior that was not properly versioned in Flash
|
|
|
|
/// Player can be enabled by setting a particular player version.
|
|
|
|
player_version: u8,
|
|
|
|
|
2019-11-12 20:05:18 +00:00
|
|
|
swf: Arc<SwfMovie>,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2021-01-09 19:36:56 +00:00
|
|
|
warn_on_unsupported_content: bool,
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
is_playing: bool,
|
2020-05-02 11:25:21 +00:00
|
|
|
needs_render: bool,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
renderer: Renderer,
|
2021-01-31 00:36:45 +00:00
|
|
|
audio: Audio,
|
|
|
|
navigator: Navigator,
|
|
|
|
storage: Storage,
|
2020-09-05 16:19:03 +00:00
|
|
|
log: Log,
|
2021-02-12 13:03:17 +00:00
|
|
|
ui: Ui,
|
2020-10-15 03:01:48 +00:00
|
|
|
video: Video,
|
2021-01-31 00:36:45 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
transform_stack: TransformStack,
|
|
|
|
|
2019-09-02 03:03:50 +00:00
|
|
|
rng: SmallRng,
|
|
|
|
|
2022-08-28 16:30:20 +00:00
|
|
|
gc_arena: Rc<RefCell<GcArena>>,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
frame_rate: f64,
|
2023-04-11 10:13:56 +00:00
|
|
|
forced_frame_rate: bool,
|
2022-08-07 23:21:21 +00:00
|
|
|
actions_since_timeout_check: u16,
|
2021-01-04 20:19:20 +00:00
|
|
|
|
2021-12-28 01:29:58 +00:00
|
|
|
frame_phase: FramePhase,
|
|
|
|
|
2023-01-31 15:45:14 +00:00
|
|
|
stub_tracker: StubCollection,
|
|
|
|
|
2021-01-04 20:19:20 +00:00
|
|
|
/// A time budget for executing frames.
|
|
|
|
/// Gained by passage of time between host frames, spent by executing SWF frames.
|
|
|
|
/// This is how we support custom SWF framerates
|
|
|
|
/// and compensate for small lags by "catching up" (up to MAX_FRAMES_PER_TICK).
|
2019-08-26 23:38:37 +00:00
|
|
|
frame_accumulator: f64,
|
2021-02-03 19:43:59 +00:00
|
|
|
recent_run_frame_timings: VecDeque<f64>,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2021-01-04 20:19:20 +00:00
|
|
|
/// Faked time passage for fooling hand-written busy-loop FPS limiters.
|
|
|
|
time_offset: u32,
|
|
|
|
|
2021-11-25 22:56:24 +00:00
|
|
|
input: InputManager,
|
|
|
|
|
2023-04-09 12:01:03 +00:00
|
|
|
mouse_in_stage: bool,
|
2023-04-27 17:34:38 +00:00
|
|
|
mouse_position: Point<Twips>,
|
2019-11-08 20:09:57 +00:00
|
|
|
|
2020-02-25 09:45:38 +00:00
|
|
|
/// The current mouse cursor icon.
|
|
|
|
mouse_cursor: MouseCursor,
|
2022-02-13 00:00:52 +00:00
|
|
|
mouse_cursor_needs_check: bool,
|
2020-02-25 09:45:38 +00:00
|
|
|
|
2020-05-27 00:52:22 +00:00
|
|
|
system: SystemProperties,
|
|
|
|
|
2020-06-18 01:18:40 +00:00
|
|
|
/// The current instance ID. Used to generate default `instanceN` names.
|
|
|
|
instance_counter: i32,
|
|
|
|
|
2020-07-08 02:06:19 +00:00
|
|
|
/// Time remaining until the next timer will fire.
|
|
|
|
time_til_next_timer: Option<f64>,
|
|
|
|
|
2022-03-16 14:09:26 +00:00
|
|
|
/// The instant at which the SWF was launched.
|
|
|
|
start_time: Instant,
|
|
|
|
|
2020-09-15 22:28:41 +00:00
|
|
|
/// The maximum amount of time that can be called before a `Error::ExecutionTimeout`
|
|
|
|
/// is raised. This defaults to 15 seconds but can be changed.
|
|
|
|
max_execution_duration: Duration,
|
|
|
|
|
2019-11-08 20:09:57 +00:00
|
|
|
/// Self-reference to ourselves.
|
|
|
|
///
|
|
|
|
/// This is a weak reference that is upgraded and handed out in various
|
|
|
|
/// contexts to other parts of the player. It can be used to ensure the
|
|
|
|
/// player lives across `await` calls in async code.
|
2022-04-27 01:25:08 +00:00
|
|
|
self_reference: Weak<Mutex<Self>>,
|
2020-12-25 21:42:14 +00:00
|
|
|
|
|
|
|
/// The current frame of the main timeline, if available.
|
|
|
|
/// The first frame is frame 1.
|
|
|
|
current_frame: Option<u16>,
|
2022-09-17 23:11:07 +00:00
|
|
|
|
|
|
|
/// How Ruffle should load movies.
|
|
|
|
load_behavior: LoadBehavior,
|
2022-10-03 23:48:40 +00:00
|
|
|
|
|
|
|
/// The root SWF URL provided to ActionScript. If None,
|
|
|
|
/// the actual loaded url will be used
|
|
|
|
spoofed_url: Option<String>,
|
2023-02-28 16:14:54 +00:00
|
|
|
|
|
|
|
/// Any compatibility rules to apply for this movie.
|
|
|
|
compatibility_rules: CompatibilityRules,
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-11-08 20:09:57 +00:00
|
|
|
impl Player {
|
2020-07-23 03:18:30 +00:00
|
|
|
/// Fetch the root movie.
|
|
|
|
///
|
|
|
|
/// This should not be called if a root movie fetch has already been kicked
|
|
|
|
/// off.
|
2021-04-21 21:26:06 +00:00
|
|
|
pub fn fetch_root_movie(
|
|
|
|
&mut self,
|
2022-06-11 08:55:17 +00:00
|
|
|
movie_url: String,
|
2021-05-03 18:11:38 +00:00
|
|
|
parameters: Vec<(String, String)>,
|
2021-05-23 00:19:45 +00:00
|
|
|
on_metadata: Box<dyn FnOnce(&swf::HeaderExt)>,
|
2021-04-21 21:26:06 +00:00
|
|
|
) {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2022-03-25 14:54:48 +00:00
|
|
|
let future = context.load_manager.load_root_movie(
|
2022-04-27 01:25:08 +00:00
|
|
|
context.player.clone(),
|
2022-06-11 08:55:17 +00:00
|
|
|
Request::get(movie_url),
|
2020-10-11 18:35:28 +00:00
|
|
|
parameters,
|
2021-04-21 21:26:06 +00:00
|
|
|
on_metadata,
|
2020-07-23 03:18:30 +00:00
|
|
|
);
|
2022-03-25 14:54:48 +00:00
|
|
|
context.navigator.spawn_future(future);
|
2020-07-23 03:18:30 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Change the root movie.
|
|
|
|
///
|
|
|
|
/// This should only be called once, as it makes no attempt at removing
|
|
|
|
/// previous stage contents. If you need to load a new root movie, you
|
|
|
|
/// should destroy and recreate the player instance.
|
2022-04-08 21:02:07 +00:00
|
|
|
pub fn set_root_movie(&mut self, movie: SwfMovie) {
|
2023-04-11 10:13:56 +00:00
|
|
|
if !self.forced_frame_rate {
|
|
|
|
self.frame_rate = movie.frame_rate().into();
|
|
|
|
}
|
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
info!(
|
2022-12-14 17:11:04 +00:00
|
|
|
"Loaded SWF version {}, resolution {}x{} @ {} FPS",
|
2021-05-23 00:19:45 +00:00
|
|
|
movie.version(),
|
2021-05-23 02:17:16 +00:00
|
|
|
movie.width(),
|
2022-12-14 17:11:04 +00:00
|
|
|
movie.height(),
|
2023-04-11 10:13:56 +00:00
|
|
|
self.frame_rate(),
|
2020-07-23 03:18:30 +00:00
|
|
|
);
|
|
|
|
|
2022-04-08 21:02:07 +00:00
|
|
|
self.swf = Arc::new(movie);
|
2020-09-04 04:15:27 +00:00
|
|
|
self.instance_counter = 0;
|
2020-07-23 03:18:30 +00:00
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2021-04-25 22:17:26 +00:00
|
|
|
context.stage.set_movie_size(
|
2021-04-17 01:53:55 +00:00
|
|
|
context.gc_context,
|
2021-05-23 02:17:16 +00:00
|
|
|
context.swf.width().to_pixels() as u32,
|
|
|
|
context.swf.height().to_pixels() as u32,
|
2021-04-17 01:53:55 +00:00
|
|
|
);
|
2022-12-31 11:06:41 +00:00
|
|
|
context
|
|
|
|
.stage
|
|
|
|
.set_movie(context.gc_context, context.swf.clone());
|
2020-10-06 02:24:59 +00:00
|
|
|
|
2023-03-26 19:12:19 +00:00
|
|
|
let global_domain = context.avm2.global_domain();
|
|
|
|
let mut global_activation =
|
|
|
|
Avm2Activation::from_domain(context.reborrow(), global_domain);
|
|
|
|
let domain = Avm2Domain::movie_domain(&mut global_activation, global_domain);
|
|
|
|
|
|
|
|
let mut activation =
|
|
|
|
Avm2Activation::from_domain(global_activation.context.reborrow(), domain);
|
2021-05-22 02:46:17 +00:00
|
|
|
|
2022-07-25 04:09:05 +00:00
|
|
|
activation
|
|
|
|
.context
|
2021-05-22 02:46:17 +00:00
|
|
|
.library
|
2022-07-25 04:09:05 +00:00
|
|
|
.library_for_movie_mut(activation.context.swf.clone())
|
2021-05-22 02:46:17 +00:00
|
|
|
.set_avm2_domain(domain);
|
2022-07-25 04:09:05 +00:00
|
|
|
activation.context.ui.set_mouse_visible(true);
|
|
|
|
|
|
|
|
let swf = activation.context.swf.clone();
|
|
|
|
let root: DisplayObject =
|
|
|
|
MovieClip::player_root_movie(&mut activation, swf.clone()).into();
|
|
|
|
|
|
|
|
// The Stage `LoaderInfo` is permanently in the 'not yet loaded' state,
|
|
|
|
// and has no associated `Loader` instance.
|
|
|
|
// However, some properties are always accessible, and take their values
|
|
|
|
// from the root SWF.
|
2022-09-03 19:30:45 +00:00
|
|
|
let stage_loader_info =
|
2022-09-17 01:34:03 +00:00
|
|
|
LoaderInfoObject::not_yet_loaded(&mut activation, swf, None, Some(root), true)
|
2022-09-03 19:30:45 +00:00
|
|
|
.expect("Failed to construct Stage LoaderInfo");
|
2022-07-25 04:09:05 +00:00
|
|
|
activation
|
|
|
|
.context
|
|
|
|
.stage
|
|
|
|
.set_loader_info(activation.context.gc_context, stage_loader_info);
|
2021-05-22 02:46:17 +00:00
|
|
|
|
2022-07-25 04:09:05 +00:00
|
|
|
drop(activation);
|
2021-05-06 22:43:23 +00:00
|
|
|
|
2020-01-20 21:38:23 +00:00
|
|
|
root.set_depth(context.gc_context, 0);
|
2020-10-11 18:35:28 +00:00
|
|
|
let flashvars = if !context.swf.parameters().is_empty() {
|
2022-08-19 10:50:44 +00:00
|
|
|
let object = ScriptObject::new(context.gc_context, None);
|
2020-10-11 18:35:28 +00:00
|
|
|
for (key, value) in context.swf.parameters().iter() {
|
|
|
|
object.define_value(
|
|
|
|
context.gc_context,
|
2021-10-05 21:12:41 +00:00
|
|
|
AvmString::new_utf8(context.gc_context, key),
|
|
|
|
AvmString::new_utf8(context.gc_context, value).into(),
|
2021-01-22 00:35:46 +00:00
|
|
|
Attribute::empty(),
|
2020-10-11 18:35:28 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
Some(object.into())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2021-04-09 01:22:43 +00:00
|
|
|
|
2022-01-22 10:32:59 +00:00
|
|
|
root.post_instantiation(context, flashvars, Instantiator::Movie, false);
|
2020-11-28 18:03:53 +00:00
|
|
|
root.set_default_root_name(context);
|
2021-04-15 03:29:12 +00:00
|
|
|
context.stage.replace_at_depth(context, root, 0);
|
2020-06-11 02:29:27 +00:00
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
// Load and parse the device font.
|
2021-04-10 19:44:41 +00:00
|
|
|
if context.library.device_font().is_none() {
|
2022-08-25 22:36:15 +00:00
|
|
|
let device_font = Self::load_device_font(context.gc_context, context.renderer);
|
|
|
|
context.library.set_device_font(device_font);
|
2021-01-23 00:03:25 +00:00
|
|
|
}
|
2020-07-23 03:18:30 +00:00
|
|
|
|
|
|
|
// Set the version parameter on the root.
|
2020-07-26 02:11:38 +00:00
|
|
|
let mut activation = Activation::from_stub(
|
2020-07-28 01:27:02 +00:00
|
|
|
context.reborrow(),
|
2020-07-02 21:37:18 +00:00
|
|
|
ActivationIdentifier::root("[Version Setter]"),
|
2020-06-30 19:57:51 +00:00
|
|
|
);
|
2020-07-27 23:19:05 +00:00
|
|
|
let object = root.object().coerce_to_object(&mut activation);
|
2020-07-28 00:57:42 +00:00
|
|
|
let version_string = activation
|
|
|
|
.context
|
|
|
|
.system
|
|
|
|
.get_version_string(activation.context.avm1);
|
2020-06-30 19:57:51 +00:00
|
|
|
object.define_value(
|
2020-07-27 23:19:05 +00:00
|
|
|
activation.context.gc_context,
|
2020-06-30 19:57:51 +00:00
|
|
|
"$version",
|
2021-10-05 21:12:41 +00:00
|
|
|
AvmString::new_utf8(activation.context.gc_context, version_string).into(),
|
2021-01-22 00:35:46 +00:00
|
|
|
Attribute::empty(),
|
2020-06-18 22:15:26 +00:00
|
|
|
);
|
2021-04-16 21:55:57 +00:00
|
|
|
|
2021-04-17 01:53:55 +00:00
|
|
|
let stage = activation.context.stage;
|
2021-04-16 21:55:57 +00:00
|
|
|
stage.build_matrices(&mut activation.context);
|
2019-10-07 08:36:05 +00:00
|
|
|
});
|
|
|
|
|
2022-09-02 22:40:59 +00:00
|
|
|
if self.swf.is_action_script_3() && self.warn_on_unsupported_content {
|
|
|
|
self.ui.display_unsupported_message();
|
|
|
|
}
|
|
|
|
|
2020-07-23 03:28:19 +00:00
|
|
|
self.audio.set_frame_rate(self.frame_rate);
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-02-03 19:43:59 +00:00
|
|
|
/// Get rough estimate of the max # of times we can update the frame.
|
|
|
|
///
|
|
|
|
/// In some cases, we might want to update several times in a row.
|
|
|
|
/// For example, if the game runs at 60FPS, but the host runs at 30FPS
|
|
|
|
/// Or if for some reason the we miss a couple of frames.
|
|
|
|
/// However, if the code is simply slow, this is the opposite of what we want;
|
|
|
|
/// If run_frame() consistently takes say 100ms, we don't want `tick` to try to "catch up",
|
|
|
|
/// as this will only make it worse.
|
|
|
|
///
|
|
|
|
/// This rough heuristic manages this job; for example if average run_frame()
|
|
|
|
/// takes more than 1/3 of frame_time, we shouldn't run it more than twice in a row.
|
|
|
|
/// This logic is far from perfect, as it doesn't take into account
|
|
|
|
/// that things like rendering also take time. But for now it's good enough.
|
|
|
|
fn max_frames_per_tick(&self) -> u32 {
|
|
|
|
const MAX_FRAMES_PER_TICK: u32 = 5;
|
|
|
|
|
|
|
|
if self.recent_run_frame_timings.is_empty() {
|
|
|
|
5
|
|
|
|
} else {
|
|
|
|
let frame_time = 1000.0 / self.frame_rate;
|
|
|
|
let average_run_frame_time = self.recent_run_frame_timings.iter().sum::<f64>()
|
|
|
|
/ self.recent_run_frame_timings.len() as f64;
|
2022-10-08 14:01:52 +00:00
|
|
|
((frame_time / average_run_frame_time) as u32).clamp(1, MAX_FRAMES_PER_TICK)
|
2021-02-03 19:43:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_frame_timing(&mut self, elapsed: f64) {
|
|
|
|
self.recent_run_frame_timings.push_back(elapsed);
|
|
|
|
if self.recent_run_frame_timings.len() >= 10 {
|
|
|
|
self.recent_run_frame_timings.pop_front();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn tick(&mut self, dt: f64) {
|
|
|
|
// Don't run until preloading is complete.
|
|
|
|
// TODO: Eventually we want to stream content similar to the Flash player.
|
|
|
|
if !self.audio.is_loading_complete() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.is_playing() {
|
|
|
|
self.frame_accumulator += dt;
|
2021-10-17 05:10:17 +00:00
|
|
|
let frame_rate = self.frame_rate;
|
|
|
|
let frame_time = 1000.0 / frame_rate;
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2021-02-03 19:43:59 +00:00
|
|
|
let max_frames_per_tick = self.max_frames_per_tick();
|
2019-08-26 23:38:37 +00:00
|
|
|
let mut frame = 0;
|
2021-02-03 19:43:59 +00:00
|
|
|
|
|
|
|
while frame < max_frames_per_tick && self.frame_accumulator >= frame_time {
|
|
|
|
let timer = Instant::now();
|
2019-08-26 23:38:37 +00:00
|
|
|
self.run_frame();
|
2021-02-03 19:43:59 +00:00
|
|
|
let elapsed = timer.elapsed().as_millis() as f64;
|
2021-01-04 20:19:20 +00:00
|
|
|
|
2021-02-03 19:43:59 +00:00
|
|
|
self.add_frame_timing(elapsed);
|
|
|
|
|
|
|
|
self.frame_accumulator -= frame_time;
|
|
|
|
frame += 1;
|
2021-01-04 20:19:20 +00:00
|
|
|
// The script probably tried implementing an FPS limiter with a busy loop.
|
|
|
|
// We fooled the busy loop by pretending that more time has passed that actually did.
|
|
|
|
// Then we need to actually pass this time, by decreasing frame_accumulator
|
|
|
|
// to delay the future frame.
|
|
|
|
if self.time_offset > 0 {
|
|
|
|
self.frame_accumulator -= self.time_offset as f64;
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-01-04 20:19:20 +00:00
|
|
|
// Now that we're done running code,
|
|
|
|
// we can stop pretending that more time passed than actually did.
|
|
|
|
// Note: update_timers(dt) doesn't need to see this either.
|
|
|
|
// Timers will run at correct times and see correct time.
|
|
|
|
// Also note that in Flash, a blocking busy loop would delay setTimeout
|
|
|
|
// and cancel some setInterval callbacks, but here busy loops don't block
|
|
|
|
// so timer callbacks won't get cancelled/delayed.
|
|
|
|
self.time_offset = 0;
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
// Sanity: If we had too many frames to tick, just reset the accumulator
|
|
|
|
// to prevent running at turbo speed.
|
|
|
|
if self.frame_accumulator >= frame_time {
|
|
|
|
self.frame_accumulator = 0.0;
|
|
|
|
}
|
|
|
|
|
2021-10-17 05:10:17 +00:00
|
|
|
// Adjust playback speed for next frame to stay in sync with timeline audio tracks ("stream" sounds).
|
|
|
|
let cur_frame_offset = self.frame_accumulator;
|
|
|
|
self.frame_accumulator += self.mutate_with_update_context(|context| {
|
|
|
|
context
|
|
|
|
.audio_manager
|
|
|
|
.audio_skew_time(context.audio, cur_frame_offset)
|
|
|
|
* 1000.0
|
|
|
|
});
|
|
|
|
|
2020-07-08 02:06:19 +00:00
|
|
|
self.update_timers(dt);
|
2023-03-18 02:25:54 +00:00
|
|
|
self.update(|context| {
|
|
|
|
StreamManager::tick(context, dt);
|
|
|
|
});
|
2019-08-26 23:38:37 +00:00
|
|
|
self.audio.tick();
|
|
|
|
}
|
|
|
|
}
|
2022-05-30 21:21:17 +00:00
|
|
|
pub fn time_til_next_timer(&self) -> Option<f64> {
|
|
|
|
self.time_til_next_timer
|
|
|
|
}
|
|
|
|
|
2019-10-29 03:35:26 +00:00
|
|
|
/// Returns the approximate duration of time until the next frame is due to run.
|
|
|
|
/// This is only an approximation to be used for sleep durations.
|
|
|
|
pub fn time_til_next_frame(&self) -> std::time::Duration {
|
|
|
|
let frame_time = 1000.0 / self.frame_rate;
|
2020-07-08 02:06:19 +00:00
|
|
|
let mut dt = if self.frame_accumulator <= 0.0 {
|
2019-10-29 03:35:26 +00:00
|
|
|
frame_time
|
|
|
|
} else if self.frame_accumulator >= frame_time {
|
|
|
|
0.0
|
|
|
|
} else {
|
|
|
|
frame_time - self.frame_accumulator
|
|
|
|
};
|
2020-07-08 02:06:19 +00:00
|
|
|
|
|
|
|
if let Some(time_til_next_timer) = self.time_til_next_timer {
|
|
|
|
dt = dt.min(time_til_next_timer)
|
|
|
|
}
|
|
|
|
|
|
|
|
dt = dt.max(0.0);
|
|
|
|
|
2019-10-29 03:35:26 +00:00
|
|
|
std::time::Duration::from_micros(dt as u64 * 1000)
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn is_playing(&self) -> bool {
|
|
|
|
self.is_playing
|
|
|
|
}
|
2021-02-20 13:40:55 +00:00
|
|
|
|
2023-04-11 19:21:44 +00:00
|
|
|
pub fn mouse_in_stage(&self) -> bool {
|
|
|
|
self.mouse_in_stage
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_mouse_in_stage(&mut self, is_in: bool) {
|
|
|
|
self.mouse_in_stage = is_in;
|
|
|
|
}
|
|
|
|
|
2022-08-02 15:35:20 +00:00
|
|
|
/// Returns the master volume of the player. 1.0 is 100% volume.
|
|
|
|
pub fn volume(&self) -> f32 {
|
|
|
|
self.audio.volume()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the master volume of the player. 1.0 is 100% volume.
|
|
|
|
pub fn set_volume(&mut self, volume: f32) {
|
|
|
|
self.audio.set_volume(volume)
|
|
|
|
}
|
|
|
|
|
2021-05-02 22:28:00 +00:00
|
|
|
pub fn prepare_context_menu(&mut self) -> Vec<ContextMenuItem> {
|
2021-04-26 20:38:51 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
if !context.stage.show_menu() {
|
|
|
|
return vec![];
|
|
|
|
}
|
|
|
|
|
2021-06-21 16:29:52 +00:00
|
|
|
// TODO: This should use a pointed display object with `.menu`
|
2022-09-14 20:29:25 +00:00
|
|
|
let root_dobj = context.stage.root_clip();
|
|
|
|
|
2023-03-12 19:01:36 +00:00
|
|
|
let menu = if let Some(Value::Object(obj)) = root_dobj.map(|root| root.object()) {
|
2022-09-14 20:29:25 +00:00
|
|
|
let mut activation = Activation::from_stub(
|
|
|
|
context.reborrow(),
|
|
|
|
ActivationIdentifier::root("[ContextMenu]"),
|
|
|
|
);
|
|
|
|
let menu_object = if let Ok(Value::Object(menu)) = obj.get("menu", &mut activation)
|
|
|
|
{
|
|
|
|
if let Ok(Value::Object(on_select)) = menu.get("onSelect", &mut activation) {
|
|
|
|
Self::run_context_menu_custom_callback(
|
|
|
|
menu,
|
|
|
|
on_select,
|
|
|
|
&mut activation.context,
|
|
|
|
);
|
2021-04-26 20:38:51 +00:00
|
|
|
}
|
2022-09-14 20:29:25 +00:00
|
|
|
Some(menu)
|
2021-05-02 22:28:00 +00:00
|
|
|
} else {
|
|
|
|
None
|
2022-09-14 20:29:25 +00:00
|
|
|
};
|
|
|
|
crate::avm1::make_context_menu_state(menu_object, &mut activation)
|
2023-03-12 19:01:36 +00:00
|
|
|
} else if let Some(Avm2Value::Object(_obj)) = root_dobj.map(|root| root.object2()) {
|
2022-09-14 20:29:25 +00:00
|
|
|
// TODO: send "menuSelect" event
|
2023-01-04 10:09:47 +00:00
|
|
|
tracing::warn!("AVM2 Context menu callbacks are not implemented");
|
2021-05-02 22:28:00 +00:00
|
|
|
|
2022-09-14 20:29:25 +00:00
|
|
|
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
|
|
|
|
|
|
|
let menu_object = root_dobj
|
2023-03-12 19:01:36 +00:00
|
|
|
.expect("Root is confirmed to exist here")
|
2022-09-14 20:29:25 +00:00
|
|
|
.as_interactive()
|
|
|
|
.map(|iobj| iobj.context_menu())
|
|
|
|
.and_then(|v| v.as_object());
|
|
|
|
|
|
|
|
crate::avm2::make_context_menu_state(menu_object, &mut activation)
|
|
|
|
} else {
|
|
|
|
// no AVM1 or AVM2 object - so just prepare the builtin items
|
|
|
|
let mut menu = ContextMenuState::new();
|
|
|
|
let builtin_items = BuiltInItemFlags::for_stage(context.stage);
|
2023-04-22 00:35:47 +00:00
|
|
|
menu.build_builtin_items(builtin_items, context.stage, context.ui.language());
|
2022-09-14 20:29:25 +00:00
|
|
|
menu
|
|
|
|
};
|
2021-06-12 15:02:27 +00:00
|
|
|
|
2021-05-02 22:28:00 +00:00
|
|
|
let ret = menu.info().clone();
|
2022-09-14 20:29:25 +00:00
|
|
|
*context.current_context_menu = Some(menu);
|
2021-05-02 22:28:00 +00:00
|
|
|
ret
|
2021-04-26 09:02:09 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-02 22:28:00 +00:00
|
|
|
pub fn clear_custom_menu_items(&mut self) {
|
2022-08-28 16:30:20 +00:00
|
|
|
self.gc_arena.borrow().mutate(|gc_context, gc_root| {
|
|
|
|
let mut root_data = gc_root.data.write(gc_context);
|
2021-05-02 22:28:00 +00:00
|
|
|
root_data.current_context_menu = None;
|
|
|
|
});
|
2021-02-20 13:40:55 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 22:28:00 +00:00
|
|
|
pub fn run_context_menu_callback(&mut self, index: usize) {
|
2021-02-20 13:40:55 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2021-05-02 22:28:00 +00:00
|
|
|
let menu = &context.current_context_menu;
|
|
|
|
if let Some(ref menu) = menu {
|
|
|
|
match menu.callback(index) {
|
|
|
|
ContextMenuCallback::Avm1 { item, callback } => {
|
|
|
|
Self::run_context_menu_custom_callback(*item, *callback, context)
|
|
|
|
}
|
|
|
|
ContextMenuCallback::Play => Self::toggle_play_root_movie(context),
|
|
|
|
ContextMenuCallback::Forward => Self::forward_root_movie(context),
|
|
|
|
ContextMenuCallback::Back => Self::back_root_movie(context),
|
|
|
|
ContextMenuCallback::Rewind => Self::rewind_root_movie(context),
|
2022-09-14 20:29:25 +00:00
|
|
|
ContextMenuCallback::Avm2 { .. } => {
|
|
|
|
// TODO: Send menuItemSelect event
|
|
|
|
}
|
2023-02-03 17:54:19 +00:00
|
|
|
ContextMenuCallback::QualityLow => {
|
|
|
|
context.stage.set_quality(context, StageQuality::Low)
|
|
|
|
}
|
|
|
|
ContextMenuCallback::QualityMedium => {
|
|
|
|
context.stage.set_quality(context, StageQuality::Medium)
|
|
|
|
}
|
|
|
|
ContextMenuCallback::QualityHigh => {
|
|
|
|
context.stage.set_quality(context, StageQuality::High)
|
|
|
|
}
|
2021-05-02 22:28:00 +00:00
|
|
|
_ => {}
|
2021-02-20 13:40:55 +00:00
|
|
|
}
|
2021-05-02 22:28:00 +00:00
|
|
|
Self::run_actions(context);
|
2021-02-20 13:40:55 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-05-02 22:28:00 +00:00
|
|
|
|
|
|
|
fn run_context_menu_custom_callback<'gc>(
|
|
|
|
item: Object<'gc>,
|
|
|
|
callback: Object<'gc>,
|
2023-01-06 23:33:31 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc>,
|
2021-05-02 22:28:00 +00:00
|
|
|
) {
|
2023-03-12 19:01:36 +00:00
|
|
|
if let Some(root_clip) = context.stage.root_clip() {
|
|
|
|
let mut activation = Activation::from_nothing(
|
|
|
|
context.reborrow(),
|
|
|
|
ActivationIdentifier::root("[Context Menu Callback]"),
|
|
|
|
root_clip,
|
|
|
|
);
|
2021-05-02 22:28:00 +00:00
|
|
|
|
2023-03-12 19:01:36 +00:00
|
|
|
// TODO: Remember to also change the first arg
|
|
|
|
// when we support contextmenu on non-root-movie
|
|
|
|
let params = vec![root_clip.object(), Value::Object(item)];
|
2021-05-02 22:28:00 +00:00
|
|
|
|
2023-03-12 19:01:36 +00:00
|
|
|
let _ = callback.call(
|
|
|
|
"[Context Menu Callback]".into(),
|
|
|
|
&mut activation,
|
|
|
|
Value::Undefined,
|
|
|
|
¶ms,
|
|
|
|
);
|
|
|
|
}
|
2021-02-20 13:40:55 +00:00
|
|
|
}
|
2021-05-02 22:28:00 +00:00
|
|
|
|
2021-08-25 08:02:53 +00:00
|
|
|
pub fn set_fullscreen(&mut self, is_fullscreen: bool) {
|
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
let display_state = if is_fullscreen {
|
|
|
|
StageDisplayState::FullScreen
|
|
|
|
} else {
|
|
|
|
StageDisplayState::Normal
|
|
|
|
};
|
|
|
|
context.stage.set_display_state(context, display_state);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-06 23:33:31 +00:00
|
|
|
fn toggle_play_root_movie(context: &mut UpdateContext<'_, '_>) {
|
2023-03-12 19:01:36 +00:00
|
|
|
if let Some(mc) = context
|
|
|
|
.stage
|
|
|
|
.root_clip()
|
|
|
|
.and_then(|root| root.as_movie_clip())
|
|
|
|
{
|
2021-05-02 22:28:00 +00:00
|
|
|
if mc.playing() {
|
|
|
|
mc.stop(context);
|
|
|
|
} else {
|
|
|
|
mc.play(context);
|
2021-02-20 13:40:55 +00:00
|
|
|
}
|
2021-05-02 22:28:00 +00:00
|
|
|
}
|
2021-02-20 13:40:55 +00:00
|
|
|
}
|
2023-01-06 23:33:31 +00:00
|
|
|
fn rewind_root_movie(context: &mut UpdateContext<'_, '_>) {
|
2023-03-12 19:01:36 +00:00
|
|
|
if let Some(mc) = context
|
|
|
|
.stage
|
|
|
|
.root_clip()
|
|
|
|
.and_then(|root| root.as_movie_clip())
|
|
|
|
{
|
2021-05-02 22:28:00 +00:00
|
|
|
mc.goto_frame(context, 1, true)
|
|
|
|
}
|
|
|
|
}
|
2023-01-06 23:33:31 +00:00
|
|
|
fn forward_root_movie(context: &mut UpdateContext<'_, '_>) {
|
2023-03-12 19:01:36 +00:00
|
|
|
if let Some(mc) = context
|
|
|
|
.stage
|
|
|
|
.root_clip()
|
|
|
|
.and_then(|root| root.as_movie_clip())
|
|
|
|
{
|
2021-05-02 22:28:00 +00:00
|
|
|
mc.next_frame(context);
|
|
|
|
}
|
|
|
|
}
|
2023-01-06 23:33:31 +00:00
|
|
|
fn back_root_movie(context: &mut UpdateContext<'_, '_>) {
|
2023-03-12 19:01:36 +00:00
|
|
|
if let Some(mc) = context
|
|
|
|
.stage
|
|
|
|
.root_clip()
|
|
|
|
.and_then(|root| root.as_movie_clip())
|
|
|
|
{
|
2021-05-02 22:28:00 +00:00
|
|
|
mc.prev_frame(context);
|
|
|
|
}
|
2021-02-20 13:40:55 +00:00
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
pub fn set_is_playing(&mut self, v: bool) {
|
|
|
|
if v {
|
|
|
|
// Allow auto-play after user gesture for web backends.
|
2020-09-23 01:18:22 +00:00
|
|
|
self.audio.play();
|
2020-09-18 22:52:35 +00:00
|
|
|
} else {
|
2020-09-23 01:18:22 +00:00
|
|
|
self.audio.pause();
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
self.is_playing = v;
|
|
|
|
}
|
|
|
|
|
2020-05-02 11:25:21 +00:00
|
|
|
pub fn needs_render(&self) -> bool {
|
|
|
|
self.needs_render
|
|
|
|
}
|
|
|
|
|
2021-04-16 21:55:57 +00:00
|
|
|
pub fn background_color(&mut self) -> Option<Color> {
|
|
|
|
self.mutate_with_update_context(|context| context.stage.background_color())
|
2021-01-13 08:54:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_background_color(&mut self, color: Option<Color>) {
|
2021-04-16 21:55:57 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
context
|
|
|
|
.stage
|
|
|
|
.set_background_color(context.gc_context, color)
|
|
|
|
})
|
2021-01-13 08:54:23 +00:00
|
|
|
}
|
|
|
|
|
2021-04-16 21:55:57 +00:00
|
|
|
pub fn letterbox(&mut self) -> Letterbox {
|
|
|
|
self.mutate_with_update_context(|context| context.stage.letterbox())
|
2021-01-06 00:41:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_letterbox(&mut self, letterbox: Letterbox) {
|
2021-04-16 21:55:57 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
context.stage.set_letterbox(context.gc_context, letterbox)
|
|
|
|
})
|
2021-01-07 07:58:21 +00:00
|
|
|
}
|
|
|
|
|
2021-04-17 01:53:55 +00:00
|
|
|
pub fn movie_width(&mut self) -> u32 {
|
2021-04-25 22:17:26 +00:00
|
|
|
self.mutate_with_update_context(|context| context.stage.movie_size().0)
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-04-17 01:53:55 +00:00
|
|
|
pub fn movie_height(&mut self) -> u32 {
|
2021-04-25 22:17:26 +00:00
|
|
|
self.mutate_with_update_context(|context| context.stage.movie_size().1)
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 05:50:18 +00:00
|
|
|
pub fn viewport_dimensions(&mut self) -> ViewportDimensions {
|
|
|
|
self.mutate_with_update_context(|context| context.renderer.viewport_dimensions())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 05:50:18 +00:00
|
|
|
pub fn set_viewport_dimensions(&mut self, dimensions: ViewportDimensions) {
|
2021-04-16 21:55:57 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2022-08-04 05:50:18 +00:00
|
|
|
context.renderer.set_viewport_dimensions(dimensions);
|
|
|
|
context.stage.build_matrices(context);
|
2021-04-16 21:55:57 +00:00
|
|
|
})
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-08-24 18:52:02 +00:00
|
|
|
pub fn set_show_menu(&mut self, show_menu: bool) {
|
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
let stage = context.stage;
|
|
|
|
stage.set_show_menu(context, show_menu);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-09-02 13:14:11 +00:00
|
|
|
pub fn set_stage_align(&mut self, stage_align: &str) {
|
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
let stage = context.stage;
|
|
|
|
if let Ok(stage_align) = StageAlign::from_str(stage_align) {
|
|
|
|
stage.set_align(context, stage_align);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-02-03 14:09:08 +00:00
|
|
|
pub fn set_quality(&mut self, quality: StageQuality) {
|
2021-09-02 13:14:11 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2023-02-03 14:16:30 +00:00
|
|
|
context.stage.set_quality(context, quality);
|
2021-09-02 13:14:11 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-22 15:52:48 +00:00
|
|
|
pub fn set_window_mode(&mut self, window_mode: &str) {
|
2022-04-13 18:55:15 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
let stage = context.stage;
|
2022-04-22 15:52:48 +00:00
|
|
|
if let Ok(window_mode) = WindowMode::from_str(window_mode) {
|
2022-04-13 18:55:15 +00:00
|
|
|
stage.set_window_mode(context, window_mode);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-09 01:18:40 +00:00
|
|
|
/// Handle an event sent into the player from the external windowing system
|
|
|
|
/// or an HTML element.
|
|
|
|
///
|
|
|
|
/// Event handling is a complicated affair, involving several different
|
|
|
|
/// concerns that need to resolve with specific priority.
|
|
|
|
///
|
2022-12-06 18:14:16 +00:00
|
|
|
/// 1. (In `avm_debug` builds)
|
|
|
|
/// If Ctrl-Alt-V is pressed, dump all AVM1 variables in the player.
|
|
|
|
/// If Ctrl-Alt-D is pressed, toggle debug output for AVM1 and AVM2.
|
|
|
|
/// If Ctrl-Alt-F is pressed, dump the display object tree.
|
|
|
|
/// 2. If the incoming event is text input or key input that could be
|
2021-10-09 01:18:40 +00:00
|
|
|
/// related to text input (e.g. pressing a letter key), we dispatch a
|
|
|
|
/// key press event onto the stage.
|
2022-12-06 18:14:16 +00:00
|
|
|
/// 3. If the event from step 3 was not handled, we check if an `EditText`
|
2021-10-09 01:18:40 +00:00
|
|
|
/// object is in focus and dispatch a text-control event to said object.
|
2022-12-06 18:14:16 +00:00
|
|
|
/// 4. If the incoming event is text input, and neither step 3 nor step 4
|
2021-10-09 01:18:40 +00:00
|
|
|
/// resulted in an event being handled, we dispatch a text input event
|
|
|
|
/// to the currently focused `EditText` (if present).
|
2022-12-06 18:14:16 +00:00
|
|
|
/// 5. Regardless of all prior event handling, we dispatch the event
|
2021-10-09 01:18:40 +00:00
|
|
|
/// through the stage normally.
|
2022-12-06 18:14:16 +00:00
|
|
|
/// 6. Then, we dispatch the event through AVM1 global listener objects.
|
|
|
|
/// 7. The AVM1 action queue is drained.
|
|
|
|
/// 8. Mouse state is updated. This triggers button rollovers, which are a
|
2021-10-09 01:18:40 +00:00
|
|
|
/// second wave of event processing.
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn handle_event(&mut self, event: PlayerEvent) {
|
2021-12-17 13:58:52 +00:00
|
|
|
let prev_is_mouse_down = self.input.is_mouse_down();
|
2021-11-25 22:56:24 +00:00
|
|
|
self.input.handle_event(&event);
|
2021-12-17 13:58:52 +00:00
|
|
|
let is_mouse_button_changed = self.input.is_mouse_down() != prev_is_mouse_down;
|
2021-11-25 22:56:24 +00:00
|
|
|
|
2020-07-25 18:40:03 +00:00
|
|
|
if cfg!(feature = "avm_debug") {
|
2021-11-24 19:37:02 +00:00
|
|
|
match event {
|
|
|
|
PlayerEvent::KeyDown {
|
|
|
|
key_code: KeyCode::V,
|
2021-11-25 22:56:24 +00:00
|
|
|
..
|
|
|
|
} if self.input.is_key_down(KeyCode::Control)
|
|
|
|
&& self.input.is_key_down(KeyCode::Alt) =>
|
|
|
|
{
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2020-07-25 18:40:03 +00:00
|
|
|
let mut dumper = VariableDumper::new(" ");
|
|
|
|
|
2020-07-26 02:11:38 +00:00
|
|
|
let mut activation = Activation::from_stub(
|
2020-07-28 01:27:02 +00:00
|
|
|
context.reborrow(),
|
2020-07-25 18:40:03 +00:00
|
|
|
ActivationIdentifier::root("[Variable Dumper]"),
|
|
|
|
);
|
|
|
|
|
2020-06-30 19:57:51 +00:00
|
|
|
dumper.print_variables(
|
2020-07-25 18:40:03 +00:00
|
|
|
"Global Variables:",
|
|
|
|
"_global",
|
2022-11-04 12:46:53 +00:00
|
|
|
&activation.context.avm1.global_object(),
|
2020-06-30 19:57:51 +00:00
|
|
|
&mut activation,
|
|
|
|
);
|
2020-07-26 02:11:38 +00:00
|
|
|
|
2022-06-08 17:24:04 +00:00
|
|
|
for display_object in activation.context.stage.iter_render_list() {
|
|
|
|
let level = display_object.depth();
|
2020-07-27 23:19:05 +00:00
|
|
|
let object = display_object.object().coerce_to_object(&mut activation);
|
2020-07-25 18:40:03 +00:00
|
|
|
dumper.print_variables(
|
2022-10-26 23:46:09 +00:00
|
|
|
&format!("Level #{level}:"),
|
|
|
|
&format!("_level{level}"),
|
2020-07-25 18:40:03 +00:00
|
|
|
&object,
|
|
|
|
&mut activation,
|
|
|
|
);
|
|
|
|
}
|
2023-01-04 10:09:47 +00:00
|
|
|
tracing::info!("Variable dump:\n{}", dumper.output());
|
2020-07-25 18:40:03 +00:00
|
|
|
});
|
|
|
|
}
|
2021-11-24 19:37:02 +00:00
|
|
|
PlayerEvent::KeyDown {
|
|
|
|
key_code: KeyCode::D,
|
2021-11-25 22:56:24 +00:00
|
|
|
..
|
|
|
|
} if self.input.is_key_down(KeyCode::Control)
|
|
|
|
&& self.input.is_key_down(KeyCode::Alt) =>
|
|
|
|
{
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2020-07-28 00:57:42 +00:00
|
|
|
if context.avm1.show_debug_output() {
|
2023-01-04 10:09:47 +00:00
|
|
|
tracing::info!(
|
2022-10-23 13:44:33 +00:00
|
|
|
"AVM Debugging turned off! Press CTRL+ALT+D to turn on again."
|
2020-07-23 20:19:52 +00:00
|
|
|
);
|
2020-07-28 00:57:42 +00:00
|
|
|
context.avm1.set_show_debug_output(false);
|
2020-07-28 03:19:43 +00:00
|
|
|
context.avm2.set_show_debug_output(false);
|
2020-07-23 20:19:52 +00:00
|
|
|
} else {
|
2023-01-04 10:09:47 +00:00
|
|
|
tracing::info!(
|
|
|
|
"AVM Debugging turned on! Press CTRL+ALT+D to turn off."
|
|
|
|
);
|
2020-07-28 00:57:42 +00:00
|
|
|
context.avm1.set_show_debug_output(true);
|
2020-07-28 03:19:43 +00:00
|
|
|
context.avm2.set_show_debug_output(true);
|
2020-07-23 20:19:52 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-11-29 17:29:49 +00:00
|
|
|
PlayerEvent::KeyDown {
|
|
|
|
key_code: KeyCode::F,
|
|
|
|
..
|
|
|
|
} if self.input.is_key_down(KeyCode::Control)
|
|
|
|
&& self.input.is_key_down(KeyCode::Alt) =>
|
|
|
|
{
|
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
context.stage.display_render_tree(0);
|
|
|
|
});
|
|
|
|
}
|
2021-11-24 19:37:02 +00:00
|
|
|
_ => {}
|
2020-07-23 20:19:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-09 06:41:04 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
// Propagate button events.
|
|
|
|
let button_event = match event {
|
|
|
|
// ASCII characters convert directly to keyPress button events.
|
|
|
|
PlayerEvent::TextInput { codepoint }
|
|
|
|
if codepoint as u32 >= 32 && codepoint as u32 <= 126 =>
|
|
|
|
{
|
|
|
|
Some(ClipEvent::KeyPress {
|
|
|
|
key_code: ButtonKeyCode::from_u8(codepoint as u8).unwrap(),
|
|
|
|
})
|
2019-12-24 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 06:41:04 +00:00
|
|
|
// Special keys have custom values for keyPress.
|
|
|
|
PlayerEvent::KeyDown { key_code, .. } => {
|
|
|
|
if let Some(key_code) = crate::events::key_code_to_button_key_code(key_code) {
|
|
|
|
Some(ClipEvent::KeyPress { key_code })
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut key_press_handled = false;
|
|
|
|
if let Some(button_event) = button_event {
|
2022-06-08 17:24:04 +00:00
|
|
|
for level in context.stage.iter_render_list() {
|
2021-11-24 19:37:02 +00:00
|
|
|
let state = if let Some(interactive) = level.as_interactive() {
|
|
|
|
interactive.handle_clip_event(context, button_event)
|
|
|
|
} else {
|
|
|
|
ClipEventResult::NotHandled
|
|
|
|
};
|
|
|
|
|
|
|
|
if state == ClipEventResult::Handled {
|
|
|
|
key_press_handled = true;
|
2021-12-09 06:41:04 +00:00
|
|
|
break;
|
2020-01-30 04:21:06 +00:00
|
|
|
}
|
2019-12-24 10:41:35 +00:00
|
|
|
}
|
2021-12-09 06:41:04 +00:00
|
|
|
}
|
2019-12-16 10:31:54 +00:00
|
|
|
|
2022-08-27 23:31:00 +00:00
|
|
|
if context.is_action_script_3() {
|
|
|
|
if let PlayerEvent::KeyDown { key_code, key_char }
|
|
|
|
| PlayerEvent::KeyUp { key_code, key_char } = event
|
|
|
|
{
|
2022-12-27 07:22:21 +00:00
|
|
|
let ctrl_key = context.input.is_key_down(KeyCode::Control);
|
|
|
|
let alt_key = context.input.is_key_down(KeyCode::Alt);
|
|
|
|
let shift_key = context.input.is_key_down(KeyCode::Shift);
|
|
|
|
|
2022-08-27 23:31:00 +00:00
|
|
|
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
|
|
|
|
|
|
|
let event_name = match event {
|
|
|
|
PlayerEvent::KeyDown { .. } => "keyDown",
|
|
|
|
PlayerEvent::KeyUp { .. } => "keyUp",
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let keyboardevent_class = activation.avm2().classes().keyboardevent;
|
|
|
|
let event_name_val: Avm2Value<'_> =
|
|
|
|
AvmString::new_utf8(activation.context.gc_context, event_name).into();
|
2022-12-27 07:22:21 +00:00
|
|
|
|
|
|
|
// TODO: keyLocation should not be a dummy value.
|
|
|
|
// ctrlKey and controlKey can be different from each other on Mac.
|
|
|
|
// commandKey should be supported.
|
2022-08-27 23:31:00 +00:00
|
|
|
let keyboard_event = keyboardevent_class
|
|
|
|
.construct(
|
|
|
|
&mut activation,
|
|
|
|
&[
|
2022-12-27 07:22:21 +00:00
|
|
|
event_name_val, /* type */
|
2022-08-27 23:31:00 +00:00
|
|
|
true.into(), /* bubbles */
|
|
|
|
false.into(), /* cancelable */
|
|
|
|
key_char.map_or(0, |c| c as u32).into(), /* charCode */
|
|
|
|
(key_code as u32).into(), /* keyCode */
|
2022-12-27 07:22:21 +00:00
|
|
|
0.into(), /* keyLocation */
|
|
|
|
ctrl_key.into(), /* ctrlKey */
|
|
|
|
alt_key.into(), /* altKey */
|
|
|
|
shift_key.into(), /* shiftKey */
|
|
|
|
ctrl_key.into(), /* controlKey */
|
2022-08-27 23:31:00 +00:00
|
|
|
],
|
|
|
|
)
|
|
|
|
.expect("Failed to construct KeyboardEvent");
|
|
|
|
|
|
|
|
let target = activation
|
|
|
|
.context
|
|
|
|
.focus_tracker
|
|
|
|
.get()
|
|
|
|
.unwrap_or_else(|| activation.context.stage.into())
|
|
|
|
.object2()
|
|
|
|
.coerce_to_object(&mut activation)
|
|
|
|
.expect("DisplayObject is not an object!");
|
|
|
|
|
2023-03-29 10:36:10 +00:00
|
|
|
Avm2::dispatch_event(&mut activation.context, keyboard_event, target);
|
2022-08-27 23:31:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-09 06:41:04 +00:00
|
|
|
// keyPress events take precedence over text input.
|
|
|
|
if !key_press_handled {
|
|
|
|
if let PlayerEvent::TextInput { codepoint } = event {
|
2021-06-13 20:25:21 +00:00
|
|
|
if let Some(text) = context.focus_tracker.get().and_then(|o| o.as_edit_text()) {
|
|
|
|
text.text_input(codepoint, context);
|
|
|
|
}
|
2021-12-09 06:41:04 +00:00
|
|
|
}
|
2023-05-10 00:28:30 +00:00
|
|
|
if let PlayerEvent::TextControl { code } = event {
|
|
|
|
if let Some(text) = context.focus_tracker.get().and_then(|o| o.as_edit_text()) {
|
|
|
|
text.text_control_input(code, context);
|
|
|
|
}
|
|
|
|
}
|
2021-06-13 20:25:21 +00:00
|
|
|
}
|
2020-10-31 21:59:52 +00:00
|
|
|
|
2021-12-09 06:41:04 +00:00
|
|
|
// Propagate clip events.
|
2020-08-22 00:03:38 +00:00
|
|
|
let (clip_event, listener) = match event {
|
|
|
|
PlayerEvent::KeyDown { .. } => {
|
|
|
|
(Some(ClipEvent::KeyDown), Some(("Key", "onKeyDown", vec![])))
|
|
|
|
}
|
|
|
|
PlayerEvent::KeyUp { .. } => {
|
|
|
|
(Some(ClipEvent::KeyUp), Some(("Key", "onKeyUp", vec![])))
|
|
|
|
}
|
|
|
|
PlayerEvent::MouseMove { .. } => (
|
|
|
|
Some(ClipEvent::MouseMove),
|
|
|
|
Some(("Mouse", "onMouseMove", vec![])),
|
|
|
|
),
|
2021-12-17 10:14:39 +00:00
|
|
|
PlayerEvent::MouseUp {
|
|
|
|
button: MouseButton::Left,
|
|
|
|
..
|
|
|
|
} => (
|
2020-08-22 00:03:38 +00:00
|
|
|
Some(ClipEvent::MouseUp),
|
|
|
|
Some(("Mouse", "onMouseUp", vec![])),
|
|
|
|
),
|
2021-12-17 10:14:39 +00:00
|
|
|
PlayerEvent::MouseDown {
|
|
|
|
button: MouseButton::Left,
|
|
|
|
..
|
|
|
|
} => (
|
2020-08-22 00:03:38 +00:00
|
|
|
Some(ClipEvent::MouseDown),
|
|
|
|
Some(("Mouse", "onMouseDown", vec![])),
|
|
|
|
),
|
|
|
|
PlayerEvent::MouseWheel { delta } => {
|
|
|
|
let delta = Value::from(delta.lines());
|
|
|
|
(None, Some(("Mouse", "onMouseWheel", vec![delta])))
|
|
|
|
}
|
|
|
|
_ => (None, None),
|
|
|
|
};
|
2019-12-16 21:58:31 +00:00
|
|
|
|
2020-08-22 00:03:38 +00:00
|
|
|
// Fire clip event on all clips.
|
|
|
|
if let Some(clip_event) = clip_event {
|
2022-06-08 17:24:04 +00:00
|
|
|
for level in context.stage.iter_render_list() {
|
2021-10-06 02:22:58 +00:00
|
|
|
if let Some(interactive) = level.as_interactive() {
|
|
|
|
interactive.handle_clip_event(context, clip_event);
|
|
|
|
}
|
2019-12-16 21:58:31 +00:00
|
|
|
}
|
2020-08-22 00:03:38 +00:00
|
|
|
}
|
2019-12-16 21:58:31 +00:00
|
|
|
|
2020-09-19 14:27:24 +00:00
|
|
|
// Fire event listener on appropriate object
|
2020-08-22 00:03:38 +00:00
|
|
|
if let Some((listener_type, event_name, args)) = listener {
|
2023-03-12 19:01:36 +00:00
|
|
|
if let Some(root_clip) = context.stage.root_clip() {
|
|
|
|
context.action_queue.queue_action(
|
|
|
|
root_clip,
|
|
|
|
ActionType::NotifyListeners {
|
|
|
|
listener: listener_type,
|
|
|
|
method: event_name,
|
|
|
|
args,
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
2020-08-22 00:03:38 +00:00
|
|
|
}
|
2021-06-16 19:17:17 +00:00
|
|
|
|
|
|
|
Self::run_actions(context);
|
2020-08-22 00:03:38 +00:00
|
|
|
});
|
2019-12-16 10:31:54 +00:00
|
|
|
|
2021-06-16 19:07:54 +00:00
|
|
|
// Update mouse state.
|
2021-12-17 13:38:06 +00:00
|
|
|
if let PlayerEvent::MouseMove { x, y }
|
2021-12-17 10:14:39 +00:00
|
|
|
| PlayerEvent::MouseDown {
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
button: MouseButton::Left,
|
|
|
|
}
|
|
|
|
| PlayerEvent::MouseUp {
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
button: MouseButton::Left,
|
|
|
|
} = event
|
2021-12-17 13:38:06 +00:00
|
|
|
{
|
|
|
|
let inverse_view_matrix =
|
|
|
|
self.mutate_with_update_context(|context| context.stage.inverse_view_matrix());
|
2023-04-27 17:34:38 +00:00
|
|
|
let prev_mouse_position = self.mouse_position;
|
|
|
|
self.mouse_position = inverse_view_matrix * Point::from_pixels(x, y);
|
2021-12-17 13:38:06 +00:00
|
|
|
|
2022-05-13 18:59:32 +00:00
|
|
|
// Update the dragged object here to keep it constantly in sync with the mouse position.
|
2022-08-09 16:07:32 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
Self::update_drag(context);
|
|
|
|
});
|
2022-05-13 18:59:32 +00:00
|
|
|
|
2023-04-27 17:34:38 +00:00
|
|
|
let is_mouse_moved = prev_mouse_position != self.mouse_position;
|
2021-12-11 21:15:55 +00:00
|
|
|
|
2021-12-17 13:38:06 +00:00
|
|
|
// This fires button rollover/press events, which should run after the above mouseMove events.
|
2021-12-11 21:15:55 +00:00
|
|
|
if self.update_mouse_state(is_mouse_button_changed, is_mouse_moved) {
|
2021-12-17 13:38:06 +00:00
|
|
|
self.needs_render = true;
|
|
|
|
}
|
2020-07-08 21:33:11 +00:00
|
|
|
}
|
2021-12-10 04:01:03 +00:00
|
|
|
|
|
|
|
if let PlayerEvent::MouseWheel { delta } = event {
|
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
if let Some(over_object) = context.mouse_over_object {
|
2023-03-07 22:38:17 +00:00
|
|
|
if context.is_action_script_3()
|
|
|
|
|| !over_object.as_displayobject().avm1_removed()
|
|
|
|
{
|
2021-12-10 04:01:03 +00:00
|
|
|
over_object.handle_clip_event(context, ClipEvent::MouseWheel { delta });
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
context
|
|
|
|
.stage
|
|
|
|
.handle_clip_event(context, ClipEvent::MouseWheel { delta });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-04-09 12:01:03 +00:00
|
|
|
|
|
|
|
if let PlayerEvent::MouseLeave = event {
|
|
|
|
if self.update_mouse_state(is_mouse_button_changed, true) {
|
|
|
|
self.needs_render = true;
|
|
|
|
}
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-21 23:37:27 +00:00
|
|
|
/// Update dragged object, if any.
|
2023-01-06 23:33:31 +00:00
|
|
|
pub fn update_drag(context: &mut UpdateContext<'_, '_>) {
|
2023-04-27 17:34:38 +00:00
|
|
|
let mouse_position = *context.mouse_position;
|
2023-03-07 22:38:17 +00:00
|
|
|
let is_action_script_3 = context.is_action_script_3();
|
2023-04-27 19:22:48 +00:00
|
|
|
if let Some(drag_object) = context.drag_object {
|
2022-08-09 16:07:32 +00:00
|
|
|
let display_object = drag_object.display_object;
|
2023-04-27 17:34:38 +00:00
|
|
|
if !is_action_script_3 && display_object.avm1_removed() {
|
2022-08-09 16:07:32 +00:00
|
|
|
// Be sure to clear the drag if the object was removed.
|
|
|
|
*context.drag_object = None;
|
2023-04-27 19:22:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let local_to_global_matrix = match display_object.parent() {
|
|
|
|
Some(parent) => parent.local_to_global_matrix(),
|
|
|
|
None => Matrix::IDENTITY,
|
|
|
|
};
|
|
|
|
let global_to_local_matrix = local_to_global_matrix.inverse().unwrap_or_default();
|
|
|
|
|
|
|
|
let new_position = if drag_object.lock_center {
|
|
|
|
global_to_local_matrix * mouse_position
|
2022-08-09 16:07:32 +00:00
|
|
|
} else {
|
2023-04-27 19:22:48 +00:00
|
|
|
let mouse_delta = mouse_position - drag_object.last_mouse_position;
|
|
|
|
// TODO: Introduce `DisplayObject::position()`?
|
|
|
|
let position = Point::new(display_object.x(), display_object.y());
|
|
|
|
position + global_to_local_matrix * mouse_delta
|
|
|
|
};
|
|
|
|
|
|
|
|
let new_position = drag_object.constraint.clamp(new_position);
|
|
|
|
|
|
|
|
// TODO: Introduce `DisplayObject::set_position()`?
|
|
|
|
display_object.set_x(context.gc_context, new_position.x);
|
|
|
|
display_object.set_y(context.gc_context, new_position.y);
|
|
|
|
drag_object.last_mouse_position = mouse_position;
|
|
|
|
|
|
|
|
// Update `_droptarget` property of dragged object.
|
|
|
|
if let Some(movie_clip) = display_object.as_movie_clip() {
|
|
|
|
// Turn the dragged object invisible so that we don't pick it.
|
|
|
|
// TODO: This could be handled via adding a `HitTestOptions::SKIP_DRAGGED`.
|
|
|
|
let was_visible = display_object.visible();
|
|
|
|
display_object.set_visible(context.gc_context, false);
|
|
|
|
// Set `_droptarget` to the object the mouse is hovering over.
|
|
|
|
let drop_target_object = run_mouse_pick(context, false);
|
|
|
|
movie_clip.set_drop_target(
|
|
|
|
context.gc_context,
|
|
|
|
drop_target_object.map(|d| d.as_displayobject()),
|
|
|
|
);
|
|
|
|
display_object.set_visible(context.gc_context, was_visible);
|
2019-12-21 23:37:27 +00:00
|
|
|
}
|
2022-08-09 16:07:32 +00:00
|
|
|
}
|
2019-12-21 23:37:27 +00:00
|
|
|
}
|
|
|
|
|
2021-06-15 20:34:19 +00:00
|
|
|
/// Updates the hover state of buttons.
|
2021-12-11 21:15:55 +00:00
|
|
|
fn update_mouse_state(&mut self, is_mouse_button_changed: bool, is_mouse_moved: bool) -> bool {
|
2020-02-25 09:45:38 +00:00
|
|
|
let mut new_cursor = self.mouse_cursor;
|
2022-02-13 00:00:52 +00:00
|
|
|
let mut mouse_cursor_needs_check = self.mouse_cursor_needs_check;
|
2023-04-11 19:21:44 +00:00
|
|
|
let mouse_in_stage = self.mouse_in_stage();
|
2021-06-15 20:34:19 +00:00
|
|
|
|
|
|
|
// Determine the display object the mouse is hovering over.
|
|
|
|
// Search through levels from top-to-bottom, returning the first display object that is under the mouse.
|
|
|
|
let needs_render = self.mutate_with_update_context(|context| {
|
2023-04-09 12:01:03 +00:00
|
|
|
let new_over_object = if mouse_in_stage {
|
|
|
|
run_mouse_pick(context, true)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2021-12-08 02:40:35 +00:00
|
|
|
let mut events: smallvec::SmallVec<[(InteractiveObject<'_>, ClipEvent); 2]> =
|
2021-06-15 20:34:19 +00:00
|
|
|
Default::default();
|
|
|
|
|
2021-12-11 21:15:55 +00:00
|
|
|
if is_mouse_moved {
|
|
|
|
events.push((
|
|
|
|
new_over_object.unwrap_or_else(|| context.stage.into()),
|
|
|
|
ClipEvent::MouseMoveInside,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-06-15 20:34:19 +00:00
|
|
|
// Cancel hover if an object is removed from the stage.
|
|
|
|
if let Some(hovered) = context.mouse_over_object {
|
2023-03-07 22:38:17 +00:00
|
|
|
if !context.is_action_script_3() && hovered.as_displayobject().avm1_removed() {
|
2021-06-15 20:34:19 +00:00
|
|
|
context.mouse_over_object = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(pressed) = context.mouse_down_object {
|
2023-03-07 22:38:17 +00:00
|
|
|
if !context.is_action_script_3() && pressed.as_displayobject().avm1_removed() {
|
2021-06-15 20:34:19 +00:00
|
|
|
context.mouse_down_object = None;
|
2019-11-13 03:00:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-13 00:00:52 +00:00
|
|
|
// Update the cursor if the object was removed from the stage.
|
|
|
|
if new_cursor != MouseCursor::Arrow {
|
|
|
|
let object_removed =
|
|
|
|
context.mouse_over_object.is_none() && context.mouse_down_object.is_none();
|
|
|
|
if !object_removed {
|
|
|
|
mouse_cursor_needs_check = false;
|
|
|
|
if is_mouse_button_changed {
|
|
|
|
// The object is pressed/released and may be removed immediately, we need to check
|
|
|
|
// in the next frame if it still exists. If it doesn't, we'll update the cursor.
|
|
|
|
mouse_cursor_needs_check = true;
|
|
|
|
}
|
|
|
|
} else if mouse_cursor_needs_check {
|
|
|
|
mouse_cursor_needs_check = false;
|
|
|
|
new_cursor = MouseCursor::Arrow;
|
|
|
|
} else if !context.input.is_mouse_down()
|
|
|
|
&& (is_mouse_moved || is_mouse_button_changed)
|
|
|
|
{
|
|
|
|
// In every other case, the cursor remains until the user interacts with the mouse again.
|
|
|
|
new_cursor = MouseCursor::Arrow;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mouse_cursor_needs_check = false;
|
|
|
|
}
|
|
|
|
|
2021-06-15 20:34:19 +00:00
|
|
|
let cur_over_object = context.mouse_over_object;
|
|
|
|
// Check if a new object has been hovered over.
|
2021-12-08 02:40:35 +00:00
|
|
|
if !InteractiveObject::option_ptr_eq(cur_over_object, new_over_object) {
|
2021-06-15 20:34:19 +00:00
|
|
|
// If the mouse button is down, the object the user clicked on grabs the focus
|
2022-02-13 00:00:52 +00:00
|
|
|
// and fires "drag" events. Other objects are ignored.
|
2021-12-17 13:58:52 +00:00
|
|
|
if context.input.is_mouse_down() {
|
2021-06-15 20:34:19 +00:00
|
|
|
context.mouse_over_object = new_over_object;
|
|
|
|
if let Some(down_object) = context.mouse_down_object {
|
2021-12-08 02:40:35 +00:00
|
|
|
if InteractiveObject::option_ptr_eq(
|
|
|
|
context.mouse_down_object,
|
|
|
|
cur_over_object,
|
|
|
|
) {
|
2021-06-15 20:34:19 +00:00
|
|
|
// Dragged from outside the clicked object to the inside.
|
2021-12-10 00:34:31 +00:00
|
|
|
events.push((
|
|
|
|
down_object,
|
|
|
|
ClipEvent::DragOut {
|
|
|
|
to: new_over_object,
|
|
|
|
},
|
|
|
|
));
|
2021-12-08 02:40:35 +00:00
|
|
|
} else if InteractiveObject::option_ptr_eq(
|
2021-06-15 20:34:19 +00:00
|
|
|
context.mouse_down_object,
|
|
|
|
new_over_object,
|
|
|
|
) {
|
|
|
|
// Dragged from inside the clicked object to the outside.
|
2021-12-10 00:34:31 +00:00
|
|
|
events.push((
|
|
|
|
down_object,
|
|
|
|
ClipEvent::DragOver {
|
|
|
|
from: cur_over_object,
|
|
|
|
},
|
|
|
|
));
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// The mouse button is up, so fire rollover states for the object we are hovering over.
|
|
|
|
// Rolled out of the previous object.
|
|
|
|
if let Some(cur_over_object) = cur_over_object {
|
2021-12-08 00:12:42 +00:00
|
|
|
events.push((
|
|
|
|
cur_over_object,
|
|
|
|
ClipEvent::RollOut {
|
2021-12-08 02:40:35 +00:00
|
|
|
to: new_over_object,
|
2021-12-08 00:12:42 +00:00
|
|
|
},
|
|
|
|
));
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
|
|
|
// Rolled over the new object.
|
|
|
|
if let Some(new_over_object) = new_over_object {
|
2021-12-05 00:15:46 +00:00
|
|
|
new_cursor = new_over_object.mouse_cursor(context);
|
2021-12-08 00:12:42 +00:00
|
|
|
events.push((
|
|
|
|
new_over_object,
|
|
|
|
ClipEvent::RollOver {
|
2021-12-08 02:40:35 +00:00
|
|
|
from: cur_over_object,
|
2021-12-08 00:12:42 +00:00
|
|
|
},
|
|
|
|
));
|
2021-06-15 20:34:19 +00:00
|
|
|
} else {
|
|
|
|
new_cursor = MouseCursor::Arrow;
|
2020-06-28 04:24:49 +00:00
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
|
|
|
context.mouse_over_object = new_over_object;
|
|
|
|
|
|
|
|
// Handle presses and releases.
|
|
|
|
if is_mouse_button_changed {
|
2021-12-17 13:58:52 +00:00
|
|
|
if context.input.is_mouse_down() {
|
2021-06-15 20:34:19 +00:00
|
|
|
// Pressed on a hovered object.
|
|
|
|
if let Some(over_object) = context.mouse_over_object {
|
|
|
|
events.push((over_object, ClipEvent::Press));
|
|
|
|
context.mouse_down_object = context.mouse_over_object;
|
2021-12-05 05:19:55 +00:00
|
|
|
} else {
|
|
|
|
events.push((context.stage.into(), ClipEvent::Press));
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
|
|
|
} else {
|
core: Fire AVM2's `mouseUp` event on the correct object.
This requires adding another notion of mouse-release events to `ClipEvent`. We now have four:
* `MouseUp` - the mouse was released, any object on the render list can handle this event ("anycast" event)
* `MouseUpInside` - the mouse was released inside this display object, only the mouse-picked target of the event can handle it
* `Release` - the mouse was released inside the last clicked display object
* `ReleaseOutside` - the mouse was released outside the last clicked display object
For those keeping score at home, in AVM2, the valid progression of events is either...
* On the same object, `mouseDown`, `mouseUp`, and `click`
* On one object, `mouseDown`, then some mouse movement that takes the cursor out of the first object, then on another object `mouseUp`, and then finally the first object gets `releaseOutside`.
2021-12-05 06:32:31 +00:00
|
|
|
if let Some(over_object) = context.mouse_over_object {
|
|
|
|
events.push((over_object, ClipEvent::MouseUpInside));
|
|
|
|
} else {
|
|
|
|
events.push((context.stage.into(), ClipEvent::MouseUpInside));
|
|
|
|
}
|
|
|
|
|
2021-12-08 02:40:35 +00:00
|
|
|
let released_inside = InteractiveObject::option_ptr_eq(
|
2021-06-15 20:34:19 +00:00
|
|
|
context.mouse_down_object,
|
|
|
|
context.mouse_over_object,
|
|
|
|
);
|
|
|
|
if released_inside {
|
|
|
|
// Released inside the clicked object.
|
|
|
|
if let Some(down_object) = context.mouse_down_object {
|
2022-02-05 14:36:13 +00:00
|
|
|
new_cursor = down_object.mouse_cursor(context);
|
2021-06-15 20:34:19 +00:00
|
|
|
events.push((down_object, ClipEvent::Release));
|
2021-12-05 05:19:55 +00:00
|
|
|
} else {
|
|
|
|
events.push((context.stage.into(), ClipEvent::Release));
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Released outside the clicked object.
|
|
|
|
if let Some(down_object) = context.mouse_down_object {
|
|
|
|
events.push((down_object, ClipEvent::ReleaseOutside));
|
2021-12-05 05:19:55 +00:00
|
|
|
} else {
|
|
|
|
events.push((context.stage.into(), ClipEvent::ReleaseOutside));
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
|
|
|
// The new object is rolled over immediately.
|
|
|
|
if let Some(over_object) = context.mouse_over_object {
|
2021-12-05 00:15:46 +00:00
|
|
|
new_cursor = over_object.mouse_cursor(context);
|
2021-12-08 00:12:42 +00:00
|
|
|
events.push((
|
|
|
|
over_object,
|
|
|
|
ClipEvent::RollOver {
|
2021-12-08 02:40:35 +00:00
|
|
|
from: cur_over_object,
|
2021-12-08 00:12:42 +00:00
|
|
|
},
|
|
|
|
));
|
2021-06-15 20:34:19 +00:00
|
|
|
} else {
|
|
|
|
new_cursor = MouseCursor::Arrow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
context.mouse_down_object = None;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2021-06-15 20:34:19 +00:00
|
|
|
// Fire any pending mouse events.
|
|
|
|
let needs_render = if events.is_empty() {
|
2019-08-26 23:38:37 +00:00
|
|
|
false
|
2021-06-15 20:34:19 +00:00
|
|
|
} else {
|
2022-05-13 18:59:32 +00:00
|
|
|
let mut refresh = false;
|
2021-06-15 20:34:19 +00:00
|
|
|
for (object, event) in events {
|
2022-05-13 18:59:32 +00:00
|
|
|
let display_object = object.as_displayobject();
|
2023-03-07 22:38:17 +00:00
|
|
|
if !display_object.avm1_removed() {
|
2021-12-08 02:40:35 +00:00
|
|
|
object.handle_clip_event(context, event);
|
2023-02-17 19:04:52 +00:00
|
|
|
if context.is_action_script_3() {
|
|
|
|
object.event_dispatch_to_avm2(context, event);
|
|
|
|
}
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
2022-05-13 18:59:32 +00:00
|
|
|
if !refresh && event.is_button_event() {
|
|
|
|
let is_button_mode = display_object.as_avm1_button().is_some()
|
|
|
|
|| display_object.as_avm2_button().is_some()
|
|
|
|
|| display_object
|
|
|
|
.as_movie_clip()
|
|
|
|
.map(|mc| mc.is_button_mode(context))
|
|
|
|
.unwrap_or_default();
|
|
|
|
if is_button_mode {
|
|
|
|
refresh = true;
|
|
|
|
}
|
|
|
|
}
|
2021-06-15 20:34:19 +00:00
|
|
|
}
|
2022-05-13 18:59:32 +00:00
|
|
|
refresh
|
2021-06-15 20:34:19 +00:00
|
|
|
};
|
|
|
|
Self::run_actions(context);
|
|
|
|
needs_render
|
2020-02-25 09:45:38 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Update mouse cursor if it has changed.
|
|
|
|
if new_cursor != self.mouse_cursor {
|
|
|
|
self.mouse_cursor = new_cursor;
|
2021-01-31 00:36:45 +00:00
|
|
|
self.ui.set_mouse_cursor(new_cursor)
|
2020-02-25 09:45:38 +00:00
|
|
|
}
|
2022-02-13 00:00:52 +00:00
|
|
|
self.mouse_cursor_needs_check = mouse_cursor_needs_check;
|
2020-02-25 09:45:38 +00:00
|
|
|
|
2021-06-15 20:34:19 +00:00
|
|
|
needs_render
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2022-09-02 22:40:59 +00:00
|
|
|
/// Preload all pending movies in the player, including the root movie.
|
2019-11-13 03:00:19 +00:00
|
|
|
///
|
2022-09-02 22:40:59 +00:00
|
|
|
/// This should be called periodically with a reasonable execution limit.
|
|
|
|
/// By default, the Player will do so after every `run_frame` using a limit
|
|
|
|
/// derived from the current frame rate and execution time. Clients that
|
|
|
|
/// want synchronous or 'lockstep' preloading may call this function with
|
|
|
|
/// an unlimited execution limit.
|
2022-09-03 02:36:49 +00:00
|
|
|
///
|
|
|
|
/// Returns true if all preloading work has completed. Clients that want to
|
|
|
|
/// simulate a particular load condition or stress chunked loading may use
|
|
|
|
/// this in lieu of an unlimited execution limit.
|
2022-09-03 03:08:13 +00:00
|
|
|
pub fn preload(&mut self, limit: &mut ExecutionLimit) -> bool {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2022-09-03 03:55:37 +00:00
|
|
|
let mut did_finish = true;
|
|
|
|
|
2023-03-29 10:36:10 +00:00
|
|
|
if let Some(root) = context
|
|
|
|
.stage
|
|
|
|
.root_clip()
|
|
|
|
.and_then(|root| root.as_movie_clip())
|
|
|
|
{
|
2022-09-03 03:55:37 +00:00
|
|
|
let was_root_movie_loaded = root.loaded_bytes() == root.total_bytes();
|
|
|
|
did_finish = root.preload(context, limit);
|
|
|
|
|
2023-03-29 10:36:10 +00:00
|
|
|
if let Some(loader_info) = root.loader_info().filter(|_| !was_root_movie_loaded) {
|
|
|
|
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
2022-09-03 03:55:37 +00:00
|
|
|
|
2023-03-29 10:36:10 +00:00
|
|
|
let progress_evt = activation.avm2().classes().progressevent.construct(
|
|
|
|
&mut activation,
|
|
|
|
&[
|
|
|
|
"progress".into(),
|
|
|
|
false.into(),
|
|
|
|
false.into(),
|
|
|
|
root.compressed_loaded_bytes().into(),
|
|
|
|
root.compressed_total_bytes().into(),
|
|
|
|
],
|
|
|
|
);
|
2022-09-03 03:55:37 +00:00
|
|
|
|
2023-03-29 10:36:10 +00:00
|
|
|
match progress_evt {
|
|
|
|
Err(e) => tracing::error!(
|
|
|
|
"Encountered AVM2 error when constructing `progress` event: {}",
|
|
|
|
e,
|
|
|
|
),
|
|
|
|
Ok(progress_evt) => {
|
|
|
|
Avm2::dispatch_event(context, progress_evt, loader_info);
|
2022-09-03 03:55:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-21 01:38:07 +00:00
|
|
|
|
2022-09-03 02:36:49 +00:00
|
|
|
if did_finish {
|
|
|
|
did_finish = LoadManager::preload_tick(context, limit);
|
|
|
|
}
|
|
|
|
|
|
|
|
did_finish
|
|
|
|
})
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2023-01-04 10:29:46 +00:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn run_frame(&mut self) {
|
2022-09-03 03:08:13 +00:00
|
|
|
let frame_time = Duration::from_nanos((750_000_000.0 / self.frame_rate) as u64);
|
2022-09-17 23:11:07 +00:00
|
|
|
let (mut execution_limit, may_execute_while_streaming) = match self.load_behavior {
|
|
|
|
LoadBehavior::Streaming => (
|
|
|
|
ExecutionLimit::with_max_ops_and_time(10000, frame_time),
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
LoadBehavior::Delayed => (
|
|
|
|
ExecutionLimit::with_max_ops_and_time(10000, frame_time),
|
|
|
|
false,
|
|
|
|
),
|
|
|
|
LoadBehavior::Blocking => (ExecutionLimit::none(), false),
|
|
|
|
};
|
|
|
|
let preload_finished = self.preload(&mut execution_limit);
|
2022-09-03 03:08:13 +00:00
|
|
|
|
2022-09-17 23:11:07 +00:00
|
|
|
if !preload_finished && !may_execute_while_streaming {
|
|
|
|
return;
|
|
|
|
}
|
2022-09-03 03:08:13 +00:00
|
|
|
|
2021-05-26 19:45:55 +00:00
|
|
|
self.update(|context| {
|
2022-01-11 01:25:39 +00:00
|
|
|
if context.is_action_script_3() {
|
|
|
|
run_all_phases_avm2(context);
|
|
|
|
} else {
|
2022-08-28 19:22:22 +00:00
|
|
|
Avm1::run_frame(context);
|
2021-05-26 19:45:55 +00:00
|
|
|
}
|
2023-03-26 21:11:36 +00:00
|
|
|
AudioManager::update_sounds(context);
|
2020-05-02 11:25:21 +00:00
|
|
|
});
|
2022-09-03 03:08:13 +00:00
|
|
|
|
2020-05-02 11:25:21 +00:00
|
|
|
self.needs_render = true;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2023-01-04 10:29:46 +00:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn render(&mut self) {
|
2023-01-29 04:38:59 +00:00
|
|
|
let invalidated = self
|
|
|
|
.gc_arena
|
|
|
|
.borrow()
|
|
|
|
.mutate(|_, gc_root| gc_root.data.read().stage.invalidated());
|
|
|
|
if invalidated {
|
|
|
|
self.update(|context| {
|
|
|
|
let stage = context.stage;
|
|
|
|
stage.broadcast_render(context);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-09-03 17:22:57 +00:00
|
|
|
let mut background_color = Color::WHITE;
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2022-09-20 16:24:09 +00:00
|
|
|
let commands = self.gc_arena.borrow().mutate(|gc_context, gc_root| {
|
2022-08-28 16:30:20 +00:00
|
|
|
let root_data = gc_root.data.read();
|
2022-09-03 17:22:57 +00:00
|
|
|
let stage = root_data.stage;
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
let mut render_context = RenderContext {
|
2023-02-28 00:01:03 +00:00
|
|
|
renderer: self.renderer.deref_mut(),
|
2022-09-20 16:24:09 +00:00
|
|
|
commands: CommandList::new(),
|
2022-08-22 20:49:58 +00:00
|
|
|
gc_context,
|
2019-12-07 02:29:36 +00:00
|
|
|
library: &root_data.library,
|
2023-02-28 00:01:03 +00:00
|
|
|
transform_stack: &mut self.transform_stack,
|
2022-09-06 21:38:48 +00:00
|
|
|
is_offscreen: false,
|
2022-09-03 17:22:57 +00:00
|
|
|
stage,
|
2019-08-26 23:38:37 +00:00
|
|
|
};
|
2019-11-13 03:00:19 +00:00
|
|
|
|
2022-09-03 17:22:57 +00:00
|
|
|
stage.render(&mut render_context);
|
|
|
|
|
|
|
|
background_color =
|
|
|
|
if stage.window_mode() != WindowMode::Transparent || stage.is_fullscreen() {
|
|
|
|
stage.background_color().unwrap_or(Color::WHITE)
|
|
|
|
} else {
|
|
|
|
Color::from_rgba(0)
|
|
|
|
};
|
2022-09-20 16:24:09 +00:00
|
|
|
|
|
|
|
render_context.commands
|
2019-08-26 23:38:37 +00:00
|
|
|
});
|
|
|
|
|
2023-02-28 00:01:03 +00:00
|
|
|
self.renderer.submit_frame(background_color, commands);
|
2022-09-03 17:22:57 +00:00
|
|
|
|
2020-05-02 11:25:21 +00:00
|
|
|
self.needs_render = false;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-12-25 21:42:14 +00:00
|
|
|
/// The current frame of the main timeline, if available.
|
|
|
|
/// The first frame is frame 1.
|
|
|
|
pub fn current_frame(&self) -> Option<u16> {
|
|
|
|
self.current_frame
|
|
|
|
}
|
|
|
|
|
2019-10-30 00:01:53 +00:00
|
|
|
pub fn audio(&self) -> &Audio {
|
|
|
|
&self.audio
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn audio_mut(&mut self) -> &mut Audio {
|
|
|
|
&mut self.audio
|
|
|
|
}
|
|
|
|
|
2022-03-25 14:54:48 +00:00
|
|
|
pub fn navigator(&self) -> &Navigator {
|
|
|
|
&self.navigator
|
|
|
|
}
|
|
|
|
|
2019-10-30 00:01:53 +00:00
|
|
|
// The frame rate of the current movie in FPS.
|
|
|
|
pub fn frame_rate(&self) -> f64 {
|
|
|
|
self.frame_rate
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn renderer(&self) -> &Renderer {
|
|
|
|
&self.renderer
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn renderer_mut(&mut self) -> &mut Renderer {
|
|
|
|
&mut self.renderer
|
|
|
|
}
|
|
|
|
|
2021-02-05 03:11:46 +00:00
|
|
|
pub fn storage(&self) -> &Storage {
|
|
|
|
&self.storage
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn storage_mut(&mut self) -> &mut Storage {
|
|
|
|
&mut self.storage
|
|
|
|
}
|
|
|
|
|
2020-10-15 23:23:36 +00:00
|
|
|
pub fn destroy(self) -> Renderer {
|
|
|
|
self.renderer
|
|
|
|
}
|
|
|
|
|
2021-02-12 13:03:17 +00:00
|
|
|
pub fn ui(&self) -> &Ui {
|
2021-01-31 00:36:45 +00:00
|
|
|
&self.ui
|
2019-12-18 21:25:54 +00:00
|
|
|
}
|
|
|
|
|
2021-02-12 13:03:17 +00:00
|
|
|
pub fn ui_mut(&mut self) -> &mut Ui {
|
2021-01-31 00:36:45 +00:00
|
|
|
&mut self.ui
|
2019-12-18 21:25:54 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 23:33:31 +00:00
|
|
|
pub fn run_actions(context: &mut UpdateContext<'_, '_>) {
|
2020-04-25 09:05:01 +00:00
|
|
|
// Note that actions can queue further actions, so a while loop is necessary here.
|
2022-11-06 05:39:11 +00:00
|
|
|
while let Some(action) = context.action_queue.pop_action() {
|
2022-12-19 23:48:50 +00:00
|
|
|
// We don't run frame actions if the clip was removed (or scheduled to be removed) after it queued the action.
|
2023-03-07 22:38:17 +00:00
|
|
|
if !action.is_unload
|
|
|
|
&& (!context.is_action_script_3()
|
|
|
|
&& (action.clip.avm1_removed() || action.clip.avm1_pending_removal()))
|
|
|
|
{
|
2019-10-26 07:58:24 +00:00
|
|
|
continue;
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2019-11-13 03:00:19 +00:00
|
|
|
|
2022-11-06 05:39:11 +00:00
|
|
|
match action.action_type {
|
2021-06-24 10:42:38 +00:00
|
|
|
// DoAction/clip event code.
|
2020-11-09 07:43:41 +00:00
|
|
|
ActionType::Normal { bytecode } | ActionType::Initialize { bytecode } => {
|
2022-11-06 05:39:11 +00:00
|
|
|
Avm1::run_stack_frame_for_action(action.clip, "[Frame]", bytecode, context);
|
2019-12-16 02:10:28 +00:00
|
|
|
}
|
2021-06-24 10:42:38 +00:00
|
|
|
// Change the prototype of a MovieClip and run constructor events.
|
2020-04-20 12:27:23 +00:00
|
|
|
ActionType::Construct {
|
|
|
|
constructor: Some(constructor),
|
|
|
|
events,
|
|
|
|
} => {
|
2020-07-01 22:09:43 +00:00
|
|
|
let mut activation = Activation::from_nothing(
|
2020-07-28 01:27:02 +00:00
|
|
|
context.reborrow(),
|
2020-07-02 21:37:18 +00:00
|
|
|
ActivationIdentifier::root("[Construct]"),
|
2022-11-06 05:39:11 +00:00
|
|
|
action.clip,
|
2020-06-30 19:57:51 +00:00
|
|
|
);
|
2021-04-01 07:36:15 +00:00
|
|
|
if let Ok(prototype) = constructor.get("prototype", &mut activation) {
|
2022-11-06 05:39:11 +00:00
|
|
|
if let Value::Object(object) = action.clip.object() {
|
2021-07-23 15:44:41 +00:00
|
|
|
object.define_value(
|
|
|
|
activation.context.gc_context,
|
|
|
|
"__proto__",
|
|
|
|
prototype,
|
2023-03-31 10:14:33 +00:00
|
|
|
Attribute::DONT_ENUM | Attribute::DONT_DELETE,
|
2021-07-23 15:44:41 +00:00
|
|
|
);
|
2020-06-30 19:57:51 +00:00
|
|
|
for event in events {
|
|
|
|
let _ = activation.run_child_frame_for_action(
|
2020-07-02 21:37:18 +00:00
|
|
|
"[Actions]",
|
2022-11-06 05:39:11 +00:00
|
|
|
action.clip,
|
2020-06-30 19:57:51 +00:00
|
|
|
event,
|
|
|
|
);
|
2020-04-20 12:27:23 +00:00
|
|
|
}
|
2020-06-30 19:57:51 +00:00
|
|
|
|
2020-07-27 23:19:05 +00:00
|
|
|
let _ = constructor.construct_on_existing(&mut activation, object, &[]);
|
2020-04-12 18:50:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 10:42:38 +00:00
|
|
|
// Run constructor events without changing the prototype.
|
2020-04-20 12:27:23 +00:00
|
|
|
ActionType::Construct {
|
|
|
|
constructor: None,
|
|
|
|
events,
|
|
|
|
} => {
|
|
|
|
for event in events {
|
2020-07-26 02:11:38 +00:00
|
|
|
Avm1::run_stack_frame_for_action(
|
2022-11-06 05:39:11 +00:00
|
|
|
action.clip,
|
2020-07-02 21:37:18 +00:00
|
|
|
"[Construct]",
|
2020-04-20 12:27:23 +00:00
|
|
|
event,
|
|
|
|
context,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 10:42:38 +00:00
|
|
|
// Event handler method call (e.g. onEnterFrame).
|
2020-01-16 19:20:07 +00:00
|
|
|
ActionType::Method { object, name, args } => {
|
2020-07-26 02:11:38 +00:00
|
|
|
Avm1::run_stack_frame_for_method(
|
2022-11-06 05:39:11 +00:00
|
|
|
action.clip,
|
2020-01-16 19:20:07 +00:00
|
|
|
object,
|
|
|
|
context,
|
2021-05-09 10:14:54 +00:00
|
|
|
name.into(),
|
2020-01-16 19:20:07 +00:00
|
|
|
&args,
|
|
|
|
);
|
2019-12-16 02:10:28 +00:00
|
|
|
}
|
2019-12-16 21:58:31 +00:00
|
|
|
|
2021-06-24 10:42:38 +00:00
|
|
|
// Event handler method call (e.g. onEnterFrame).
|
2019-12-18 16:45:20 +00:00
|
|
|
ActionType::NotifyListeners {
|
|
|
|
listener,
|
|
|
|
method,
|
|
|
|
args,
|
|
|
|
} => {
|
2019-12-18 19:30:21 +00:00
|
|
|
// A native function ends up resolving immediately,
|
|
|
|
// so this doesn't require any further execution.
|
2020-07-26 02:11:38 +00:00
|
|
|
Avm1::notify_system_listeners(
|
2022-11-06 05:39:11 +00:00
|
|
|
action.clip,
|
2019-12-16 21:58:31 +00:00
|
|
|
context,
|
2021-05-09 10:14:54 +00:00
|
|
|
listener.into(),
|
|
|
|
method.into(),
|
2019-12-18 16:45:20 +00:00
|
|
|
&args,
|
2019-12-16 21:58:31 +00:00
|
|
|
);
|
|
|
|
}
|
2019-10-29 04:07:47 +00:00
|
|
|
}
|
2022-12-21 02:39:06 +00:00
|
|
|
|
|
|
|
// AVM1 bytecode may leave the stack unbalanced, so do not let garbage values accumulate
|
|
|
|
// across multiple executions and/or frames.
|
|
|
|
context.avm1.clear_stack();
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
/// Runs the closure `f` with an `UpdateContext`.
|
|
|
|
/// This takes cares of populating the `UpdateContext` struct, avoiding borrow issues.
|
2022-04-27 00:53:52 +00:00
|
|
|
pub(crate) fn mutate_with_update_context<F, R>(&mut self, f: F) -> R
|
2019-12-08 18:49:23 +00:00
|
|
|
where
|
2023-01-06 23:33:31 +00:00
|
|
|
F: for<'a, 'gc> FnOnce(&mut UpdateContext<'a, 'gc>) -> R,
|
2019-12-08 18:49:23 +00:00
|
|
|
{
|
2022-08-28 16:30:20 +00:00
|
|
|
self.gc_arena.borrow().mutate(|gc_context, gc_root| {
|
|
|
|
let mut root_data = gc_root.data.write(gc_context);
|
2019-12-08 18:49:23 +00:00
|
|
|
let mouse_hovered_object = root_data.mouse_hovered_object;
|
2021-06-15 18:27:55 +00:00
|
|
|
let mouse_pressed_object = root_data.mouse_pressed_object;
|
2020-10-29 23:09:57 +00:00
|
|
|
let focus_tracker = root_data.focus_tracker;
|
2020-06-23 04:07:27 +00:00
|
|
|
let (
|
2021-04-15 03:29:12 +00:00
|
|
|
stage,
|
2020-06-23 04:07:27 +00:00
|
|
|
library,
|
|
|
|
action_queue,
|
2023-03-15 22:46:43 +00:00
|
|
|
interner,
|
2020-02-04 02:22:44 +00:00
|
|
|
avm1,
|
|
|
|
avm2,
|
2020-06-23 04:07:27 +00:00
|
|
|
drag_object,
|
|
|
|
load_manager,
|
2022-08-28 20:45:10 +00:00
|
|
|
avm1_shared_objects,
|
|
|
|
avm2_shared_objects,
|
2020-06-23 04:07:27 +00:00
|
|
|
unbound_text_fields,
|
2020-07-08 02:06:19 +00:00
|
|
|
timers,
|
2021-05-02 22:28:00 +00:00
|
|
|
current_context_menu,
|
2020-08-27 22:16:53 +00:00
|
|
|
external_interface,
|
2021-01-23 02:03:59 +00:00
|
|
|
audio_manager,
|
2023-03-18 02:25:54 +00:00
|
|
|
stream_manager,
|
2020-06-23 04:07:27 +00:00
|
|
|
) = root_data.update_context_params();
|
2019-12-02 02:00:01 +00:00
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
let mut update_context = UpdateContext {
|
2021-10-21 16:52:27 +00:00
|
|
|
player_version: self.player_version,
|
|
|
|
swf: &self.swf,
|
2019-12-08 18:49:23 +00:00
|
|
|
library,
|
2021-10-21 16:52:27 +00:00
|
|
|
rng: &mut self.rng,
|
|
|
|
renderer: self.renderer.deref_mut(),
|
|
|
|
audio: self.audio.deref_mut(),
|
|
|
|
navigator: self.navigator.deref_mut(),
|
|
|
|
ui: self.ui.deref_mut(),
|
2019-12-08 18:49:23 +00:00
|
|
|
action_queue,
|
|
|
|
gc_context,
|
2023-03-15 22:46:43 +00:00
|
|
|
interner,
|
2021-04-15 03:29:12 +00:00
|
|
|
stage,
|
2021-06-15 20:34:19 +00:00
|
|
|
mouse_over_object: mouse_hovered_object,
|
|
|
|
mouse_down_object: mouse_pressed_object,
|
2021-11-25 22:56:24 +00:00
|
|
|
input: &self.input,
|
2023-04-27 17:34:38 +00:00
|
|
|
mouse_position: &self.mouse_position,
|
2019-12-21 23:37:27 +00:00
|
|
|
drag_object,
|
2021-10-21 16:52:27 +00:00
|
|
|
player: self.self_reference.clone(),
|
2020-01-10 23:28:49 +00:00
|
|
|
load_manager,
|
2021-10-21 16:52:27 +00:00
|
|
|
system: &mut self.system,
|
|
|
|
instance_counter: &mut self.instance_counter,
|
|
|
|
storage: self.storage.deref_mut(),
|
|
|
|
log: self.log.deref_mut(),
|
|
|
|
video: self.video.deref_mut(),
|
2022-08-28 20:45:10 +00:00
|
|
|
avm1_shared_objects,
|
|
|
|
avm2_shared_objects,
|
2020-06-23 04:07:27 +00:00
|
|
|
unbound_text_fields,
|
2020-07-08 02:06:19 +00:00
|
|
|
timers,
|
2021-05-02 22:28:00 +00:00
|
|
|
current_context_menu,
|
2021-10-21 16:52:27 +00:00
|
|
|
needs_render: &mut self.needs_render,
|
2020-07-28 00:57:42 +00:00
|
|
|
avm1,
|
2020-07-28 03:19:43 +00:00
|
|
|
avm2,
|
2020-08-27 21:47:08 +00:00
|
|
|
external_interface,
|
2022-03-16 14:09:26 +00:00
|
|
|
start_time: self.start_time,
|
2020-09-15 22:28:41 +00:00
|
|
|
update_start: Instant::now(),
|
2021-10-21 16:52:27 +00:00
|
|
|
max_execution_duration: self.max_execution_duration,
|
2020-10-29 23:09:57 +00:00
|
|
|
focus_tracker,
|
2021-01-04 20:19:20 +00:00
|
|
|
times_get_time_called: 0,
|
2021-10-21 16:52:27 +00:00
|
|
|
time_offset: &mut self.time_offset,
|
2021-01-23 02:03:59 +00:00
|
|
|
audio_manager,
|
2021-10-21 16:52:27 +00:00
|
|
|
frame_rate: &mut self.frame_rate,
|
2023-04-11 10:13:56 +00:00
|
|
|
forced_frame_rate: self.forced_frame_rate,
|
2022-08-07 23:21:21 +00:00
|
|
|
actions_since_timeout_check: &mut self.actions_since_timeout_check,
|
2021-12-28 01:29:58 +00:00
|
|
|
frame_phase: &mut self.frame_phase,
|
2023-01-31 15:45:14 +00:00
|
|
|
stub_tracker: &mut self.stub_tracker,
|
2023-03-18 02:25:54 +00:00
|
|
|
stream_manager,
|
2019-12-08 18:49:23 +00:00
|
|
|
};
|
|
|
|
|
2023-04-27 17:34:38 +00:00
|
|
|
let prev_frame_rate = *update_context.frame_rate;
|
2021-04-21 01:20:45 +00:00
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
let ret = f(&mut update_context);
|
2019-12-08 18:49:23 +00:00
|
|
|
|
2021-04-21 01:20:45 +00:00
|
|
|
// If we changed the framerate, let the audio handler now.
|
|
|
|
#[allow(clippy::float_cmp)]
|
2023-04-27 17:34:38 +00:00
|
|
|
if *update_context.frame_rate != prev_frame_rate {
|
|
|
|
update_context
|
|
|
|
.audio
|
|
|
|
.set_frame_rate(*update_context.frame_rate);
|
2021-04-21 01:20:45 +00:00
|
|
|
}
|
|
|
|
|
2021-10-21 16:52:27 +00:00
|
|
|
self.current_frame = update_context
|
2021-04-15 03:29:12 +00:00
|
|
|
.stage
|
2021-04-17 19:38:11 +00:00
|
|
|
.root_clip()
|
2023-03-12 19:01:36 +00:00
|
|
|
.and_then(|root| root.as_movie_clip())
|
2020-12-25 21:42:14 +00:00
|
|
|
.map(|clip| clip.current_frame());
|
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
// Hovered object may have been updated; copy it back to the GC root.
|
2021-06-15 20:34:19 +00:00
|
|
|
let mouse_hovered_object = update_context.mouse_over_object;
|
|
|
|
let mouse_pressed_object = update_context.mouse_down_object;
|
2021-06-15 18:27:55 +00:00
|
|
|
root_data.mouse_hovered_object = mouse_hovered_object;
|
|
|
|
root_data.mouse_pressed_object = mouse_pressed_object;
|
2020-12-25 21:42:14 +00:00
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
ret
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-23 20:38:54 +00:00
|
|
|
pub fn load_device_font<'gc>(
|
2019-12-17 05:21:59 +00:00
|
|
|
gc_context: gc_arena::MutationContext<'gc, '_>,
|
2020-07-23 03:18:30 +00:00
|
|
|
renderer: &mut dyn RenderBackend,
|
2022-08-25 22:36:15 +00:00
|
|
|
) -> Font<'gc> {
|
|
|
|
const DEVICE_FONT_TAG: &[u8] = include_bytes!("../assets/noto-sans-definefont3.bin");
|
|
|
|
let mut reader = swf::read::Reader::new(DEVICE_FONT_TAG, 8);
|
|
|
|
Font::from_swf_tag(
|
2021-01-20 20:46:22 +00:00
|
|
|
gc_context,
|
|
|
|
renderer,
|
2022-08-25 22:36:15 +00:00
|
|
|
reader
|
|
|
|
.read_define_font_2(3)
|
|
|
|
.expect("Built-in font should compile"),
|
2021-01-20 20:46:22 +00:00
|
|
|
reader.encoding(),
|
2022-08-25 22:36:15 +00:00
|
|
|
)
|
2019-10-08 04:02:09 +00:00
|
|
|
}
|
2019-11-10 03:40:07 +00:00
|
|
|
|
|
|
|
/// Update the current state of the player.
|
|
|
|
///
|
|
|
|
/// The given function will be called with the current stage root, current
|
|
|
|
/// mouse hover node, AVM, and an update context.
|
|
|
|
///
|
|
|
|
/// This particular function runs necessary post-update bookkeeping, such
|
|
|
|
/// as executing any actions queued on the update context, keeping the
|
|
|
|
/// hover state up to date, and running garbage collection.
|
|
|
|
pub fn update<F, R>(&mut self, func: F) -> R
|
|
|
|
where
|
2023-01-06 23:33:31 +00:00
|
|
|
F: for<'a, 'gc> FnOnce(&mut UpdateContext<'a, 'gc>) -> R,
|
2019-11-10 03:40:07 +00:00
|
|
|
{
|
2020-07-28 03:19:43 +00:00
|
|
|
let rval = self.mutate_with_update_context(|context| {
|
|
|
|
let rval = func(context);
|
2019-11-10 03:40:07 +00:00
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
Self::run_actions(context);
|
2019-11-10 03:40:07 +00:00
|
|
|
|
|
|
|
rval
|
|
|
|
});
|
|
|
|
|
|
|
|
// Update mouse state (check for new hovered button, etc.)
|
2022-08-09 16:07:32 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
Self::update_drag(context);
|
|
|
|
});
|
2021-12-11 21:15:55 +00:00
|
|
|
self.update_mouse_state(false, false);
|
2019-11-10 03:40:07 +00:00
|
|
|
|
|
|
|
// GC
|
2022-08-28 16:30:20 +00:00
|
|
|
self.gc_arena.borrow_mut().collect_debt();
|
2019-11-10 03:40:07 +00:00
|
|
|
|
|
|
|
rval
|
|
|
|
}
|
2020-06-22 00:59:38 +00:00
|
|
|
|
|
|
|
pub fn flush_shared_objects(&mut self) {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.update(|context| {
|
2023-03-12 19:50:29 +00:00
|
|
|
if let Some(mut avm1_activation) =
|
|
|
|
Activation::try_from_stub(context.reborrow(), ActivationIdentifier::root("[Flush]"))
|
|
|
|
{
|
|
|
|
for so in avm1_activation.context.avm1_shared_objects.clone().values() {
|
|
|
|
if let Err(e) = crate::avm1::flush(&mut avm1_activation, *so, &[]) {
|
|
|
|
tracing::error!("Error flushing AVM1 shared object `{:?}`: {:?}", so, e);
|
|
|
|
}
|
2022-08-28 20:45:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-12 19:50:29 +00:00
|
|
|
let mut avm2_activation = Avm2Activation::from_nothing(context.reborrow());
|
2022-08-28 20:45:10 +00:00
|
|
|
for so in avm2_activation.context.avm2_shared_objects.clone().values() {
|
2022-12-19 10:38:07 +00:00
|
|
|
if let Err(e) = crate::avm2::globals::flash::net::shared_object::flush(
|
2022-08-28 20:45:10 +00:00
|
|
|
&mut avm2_activation,
|
|
|
|
Some(*so),
|
|
|
|
&[],
|
|
|
|
) {
|
2023-01-04 10:09:47 +00:00
|
|
|
tracing::error!("Error flushing AVM2 shared object `{:?}`: {:?}", so, e);
|
2022-08-28 20:45:10 +00:00
|
|
|
}
|
2020-06-30 19:57:51 +00:00
|
|
|
}
|
2020-06-22 00:59:38 +00:00
|
|
|
});
|
|
|
|
}
|
2020-07-08 02:06:19 +00:00
|
|
|
|
|
|
|
/// Update all AVM-based timers (such as created via setInterval).
|
|
|
|
/// Returns the approximate amount of time until the next timer tick.
|
|
|
|
pub fn update_timers(&mut self, dt: f64) {
|
2020-07-28 00:57:42 +00:00
|
|
|
self.time_til_next_timer =
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| Timers::update_timers(context, dt));
|
2020-07-08 02:06:19 +00:00
|
|
|
}
|
2020-08-22 00:03:38 +00:00
|
|
|
|
|
|
|
/// Returns whether this player consumes mouse wheel events.
|
|
|
|
/// Used by web to prevent scrolling.
|
|
|
|
pub fn should_prevent_scrolling(&mut self) -> bool {
|
|
|
|
self.mutate_with_update_context(|context| context.avm1.has_mouse_listener())
|
|
|
|
}
|
2020-08-27 21:27:54 +00:00
|
|
|
|
|
|
|
pub fn add_external_interface(&mut self, provider: Box<dyn ExternalInterfaceProvider>) {
|
2020-08-27 22:16:53 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
context.external_interface.add_provider(provider)
|
|
|
|
});
|
2020-08-27 21:27:54 +00:00
|
|
|
}
|
2020-08-28 10:24:19 +00:00
|
|
|
|
2020-08-28 15:24:26 +00:00
|
|
|
pub fn call_internal_interface(
|
|
|
|
&mut self,
|
|
|
|
name: &str,
|
|
|
|
args: impl IntoIterator<Item = ExternalValue>,
|
|
|
|
) -> ExternalValue {
|
2020-08-28 10:24:19 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
if let Some(callback) = context.external_interface.get_callback(name) {
|
2020-08-28 15:24:26 +00:00
|
|
|
callback.call(context, name, args)
|
|
|
|
} else {
|
|
|
|
ExternalValue::Null
|
2020-08-28 10:24:19 +00:00
|
|
|
}
|
2020-08-28 15:24:26 +00:00
|
|
|
})
|
2020-08-28 10:24:19 +00:00
|
|
|
}
|
2020-09-05 19:06:17 +00:00
|
|
|
|
2022-10-03 23:48:40 +00:00
|
|
|
pub fn spoofed_url(&self) -> Option<&str> {
|
|
|
|
self.spoofed_url.as_deref()
|
|
|
|
}
|
|
|
|
|
2023-02-28 16:14:54 +00:00
|
|
|
pub fn compatibility_rules(&self) -> &CompatibilityRules {
|
|
|
|
&self.compatibility_rules
|
|
|
|
}
|
|
|
|
|
2020-09-05 19:10:46 +00:00
|
|
|
pub fn log_backend(&self) -> &Log {
|
|
|
|
&self.log
|
2020-09-05 19:06:17 +00:00
|
|
|
}
|
2020-09-15 22:28:41 +00:00
|
|
|
|
|
|
|
pub fn max_execution_duration(&self) -> Duration {
|
|
|
|
self.max_execution_duration
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_max_execution_duration(&mut self, max_execution_duration: Duration) {
|
|
|
|
self.max_execution_duration = max_execution_duration
|
|
|
|
}
|
2022-08-28 16:30:20 +00:00
|
|
|
|
|
|
|
pub fn callstack(&self) -> StaticCallstack {
|
|
|
|
StaticCallstack {
|
|
|
|
arena: Rc::downgrade(&self.gc_arena),
|
|
|
|
}
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2019-12-21 23:37:27 +00:00
|
|
|
|
2022-04-26 00:56:57 +00:00
|
|
|
/// Player factory, which can be used to configure the aspects of a Ruffle player.
|
|
|
|
pub struct PlayerBuilder {
|
2022-04-26 03:35:56 +00:00
|
|
|
movie: Option<SwfMovie>,
|
|
|
|
|
|
|
|
// Backends
|
2022-04-26 00:56:57 +00:00
|
|
|
audio: Option<Audio>,
|
|
|
|
log: Option<Log>,
|
|
|
|
navigator: Option<Navigator>,
|
|
|
|
renderer: Option<Renderer>,
|
|
|
|
storage: Option<Storage>,
|
|
|
|
ui: Option<Ui>,
|
|
|
|
video: Option<Video>,
|
2022-04-26 03:35:56 +00:00
|
|
|
|
|
|
|
// Misc. player configuration
|
|
|
|
autoplay: bool,
|
2023-02-04 11:55:09 +00:00
|
|
|
scale_mode: StageScaleMode,
|
|
|
|
forced_scale_mode: bool,
|
2022-05-20 06:01:23 +00:00
|
|
|
fullscreen: bool,
|
2022-04-26 03:35:56 +00:00
|
|
|
letterbox: Letterbox,
|
|
|
|
max_execution_duration: Duration,
|
|
|
|
viewport_width: u32,
|
|
|
|
viewport_height: u32,
|
|
|
|
viewport_scale_factor: f64,
|
|
|
|
warn_on_unsupported_content: bool,
|
2022-09-17 23:11:07 +00:00
|
|
|
load_behavior: LoadBehavior,
|
2022-10-03 23:48:40 +00:00
|
|
|
spoofed_url: Option<String>,
|
2023-02-28 16:14:54 +00:00
|
|
|
compatibility_rules: CompatibilityRules,
|
2022-12-29 21:44:20 +00:00
|
|
|
player_version: Option<u8>,
|
2023-02-03 15:53:24 +00:00
|
|
|
quality: StageQuality,
|
2023-02-09 19:06:17 +00:00
|
|
|
sandbox_type: SandboxType,
|
2023-04-11 10:13:56 +00:00
|
|
|
frame_rate: Option<f64>,
|
2022-04-26 00:56:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PlayerBuilder {
|
|
|
|
/// Generates the base configuration for creating a player.
|
|
|
|
///
|
|
|
|
/// All settings will be at their defaults, and "null" backends will be used. The settings
|
|
|
|
/// can be changed by chaining the configuration methods.
|
|
|
|
#[inline]
|
|
|
|
pub fn new() -> Self {
|
2022-04-26 03:35:56 +00:00
|
|
|
Self {
|
|
|
|
movie: None,
|
|
|
|
|
|
|
|
audio: None,
|
|
|
|
log: None,
|
|
|
|
navigator: None,
|
|
|
|
renderer: None,
|
|
|
|
storage: None,
|
|
|
|
ui: None,
|
|
|
|
video: None,
|
|
|
|
|
|
|
|
autoplay: false,
|
2023-02-04 11:55:09 +00:00
|
|
|
scale_mode: StageScaleMode::ShowAll,
|
|
|
|
forced_scale_mode: false,
|
2022-05-20 06:01:23 +00:00
|
|
|
fullscreen: false,
|
2022-04-26 03:35:56 +00:00
|
|
|
// Disable script timeout in debug builds by default.
|
|
|
|
letterbox: Letterbox::Fullscreen,
|
|
|
|
max_execution_duration: Duration::from_secs(if cfg!(debug_assertions) {
|
|
|
|
u64::MAX
|
|
|
|
} else {
|
|
|
|
15
|
|
|
|
}),
|
|
|
|
viewport_width: 550,
|
|
|
|
viewport_height: 400,
|
|
|
|
viewport_scale_factor: 1.0,
|
|
|
|
warn_on_unsupported_content: true,
|
2022-09-17 23:11:07 +00:00
|
|
|
load_behavior: LoadBehavior::Streaming,
|
2022-10-03 23:48:40 +00:00
|
|
|
spoofed_url: None,
|
2023-02-28 16:24:27 +00:00
|
|
|
compatibility_rules: CompatibilityRules::default(),
|
2022-12-29 21:44:20 +00:00
|
|
|
player_version: None,
|
2023-02-03 15:53:24 +00:00
|
|
|
quality: StageQuality::High,
|
2023-02-09 19:06:17 +00:00
|
|
|
sandbox_type: SandboxType::LocalTrusted,
|
2023-04-11 10:13:56 +00:00
|
|
|
frame_rate: None,
|
2022-04-26 03:35:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Configures the player to play an already-loaded movie.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_movie(mut self, movie: SwfMovie) -> Self {
|
|
|
|
self.movie = Some(movie);
|
|
|
|
self
|
2022-04-26 00:56:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the audio backend of the player.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_audio(mut self, audio: impl 'static + AudioBackend) -> Self {
|
|
|
|
self.audio = Some(Box::new(audio));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the logging backend of the player.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_log(mut self, log: impl 'static + LogBackend) -> Self {
|
|
|
|
self.log = Some(Box::new(log));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the navigator backend of the player.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_navigator(mut self, navigator: impl 'static + NavigatorBackend) -> Self {
|
|
|
|
self.navigator = Some(Box::new(navigator));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the rendering backend of the player.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_renderer(mut self, renderer: impl 'static + RenderBackend) -> Self {
|
|
|
|
self.renderer = Some(Box::new(renderer));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the storage backend of the player.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_storage(mut self, storage: impl 'static + StorageBackend) -> Self {
|
|
|
|
self.storage = Some(Box::new(storage));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the UI backend of the player.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_ui(mut self, ui: impl 'static + UiBackend) -> Self {
|
|
|
|
self.ui = Some(Box::new(ui));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the video backend of the player.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_video(mut self, video: impl 'static + VideoBackend) -> Self {
|
|
|
|
self.video = Some(Box::new(video));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-04-26 03:35:56 +00:00
|
|
|
/// Sets whether the movie will start playing immediately upon load.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_autoplay(mut self, autoplay: bool) -> Self {
|
|
|
|
self.autoplay = autoplay;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the letterbox setting for the player.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_letterbox(mut self, letterbox: Letterbox) -> Self {
|
|
|
|
self.letterbox = letterbox;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the maximum execution time of ActionScript code.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_max_execution_duration(mut self, duration: Duration) -> Self {
|
|
|
|
self.max_execution_duration = duration;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Configures the player to warn if unsupported content is detected (ActionScript 3.0).
|
|
|
|
#[inline]
|
|
|
|
pub fn with_warn_on_unsupported_content(mut self, value: bool) -> Self {
|
|
|
|
self.warn_on_unsupported_content = value;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the dimensions of the stage.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_viewport_dimensions(
|
|
|
|
mut self,
|
|
|
|
width: u32,
|
|
|
|
height: u32,
|
|
|
|
dpi_scale_factor: f64,
|
|
|
|
) -> Self {
|
|
|
|
self.viewport_width = width;
|
|
|
|
self.viewport_height = height;
|
|
|
|
self.viewport_scale_factor = dpi_scale_factor;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-02-04 11:55:09 +00:00
|
|
|
/// Sets the stage scale mode and optionally prevents movies from changing it.
|
|
|
|
#[inline]
|
|
|
|
pub fn with_scale_mode(mut self, scale: StageScaleMode, force: bool) -> Self {
|
|
|
|
self.scale_mode = scale;
|
|
|
|
self.forced_scale_mode = force;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-02-03 15:53:24 +00:00
|
|
|
/// Sets whether the stage is fullscreen.
|
2022-05-20 06:01:23 +00:00
|
|
|
pub fn with_fullscreen(mut self, fullscreen: bool) -> Self {
|
|
|
|
self.fullscreen = fullscreen;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-02-03 15:53:24 +00:00
|
|
|
/// Sets the default stage quality
|
|
|
|
pub fn with_quality(mut self, quality: StageQuality) -> Self {
|
|
|
|
self.quality = quality;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-09-17 23:11:07 +00:00
|
|
|
/// Configures how the root movie should be loaded.
|
|
|
|
pub fn with_load_behavior(mut self, load_behavior: LoadBehavior) -> Self {
|
|
|
|
self.load_behavior = load_behavior;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-10-03 23:48:40 +00:00
|
|
|
/// Sets the root SWF URL provided to ActionScript.
|
|
|
|
pub fn with_spoofed_url(mut self, url: Option<String>) -> Self {
|
|
|
|
self.spoofed_url = url;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-02-28 19:09:33 +00:00
|
|
|
/// Sets the compatibility rules to use with this movie.
|
|
|
|
pub fn with_compatibility_rules(mut self, compatibility_rules: CompatibilityRules) -> Self {
|
|
|
|
self.compatibility_rules = compatibility_rules;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-04-11 10:13:56 +00:00
|
|
|
/// Configures the target player version.
|
2022-12-29 21:44:20 +00:00
|
|
|
pub fn with_player_version(mut self, version: Option<u8>) -> Self {
|
|
|
|
self.player_version = version;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-04-11 10:13:56 +00:00
|
|
|
/// Configures the security sandbox type (default is `SandboxType::LocalTrusted`)
|
2023-02-09 19:06:17 +00:00
|
|
|
pub fn with_sandbox_type(mut self, sandbox_type: SandboxType) -> Self {
|
|
|
|
self.sandbox_type = sandbox_type;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-04-11 10:13:56 +00:00
|
|
|
/// Sets and locks the player's frame rate. If None is provided, this has no effect.
|
|
|
|
pub fn with_frame_rate(mut self, frame_rate: Option<f64>) -> Self {
|
|
|
|
self.frame_rate = frame_rate;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-03-15 22:46:43 +00:00
|
|
|
fn create_gc_root<'gc>(
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
player_version: u8,
|
|
|
|
fullscreen: bool,
|
|
|
|
fake_movie: Arc<SwfMovie>,
|
|
|
|
) -> GcRoot<'gc> {
|
|
|
|
let mut interner = AvmStringInterner::new();
|
|
|
|
let mut init = GcContext {
|
|
|
|
gc_context,
|
|
|
|
interner: &mut interner,
|
|
|
|
};
|
|
|
|
|
|
|
|
GcRoot {
|
|
|
|
callstack: GcCell::allocate(gc_context, GcCallstack::default()),
|
|
|
|
data: GcCell::allocate(
|
|
|
|
gc_context,
|
|
|
|
GcRootData {
|
|
|
|
audio_manager: AudioManager::new(),
|
|
|
|
action_queue: ActionQueue::new(),
|
|
|
|
avm1: Avm1::new(&mut init, player_version),
|
2023-05-05 05:14:01 +00:00
|
|
|
avm2: Avm2::new(&mut init, player_version),
|
2023-03-15 22:46:43 +00:00
|
|
|
interner,
|
|
|
|
current_context_menu: None,
|
|
|
|
drag_object: None,
|
|
|
|
external_interface: ExternalInterface::new(),
|
|
|
|
focus_tracker: FocusTracker::new(gc_context),
|
|
|
|
library: Library::empty(),
|
|
|
|
load_manager: LoadManager::new(),
|
|
|
|
mouse_hovered_object: None,
|
|
|
|
mouse_pressed_object: None,
|
|
|
|
avm1_shared_objects: HashMap::new(),
|
|
|
|
avm2_shared_objects: HashMap::new(),
|
|
|
|
stage: Stage::empty(gc_context, fullscreen, fake_movie),
|
|
|
|
timers: Timers::new(),
|
|
|
|
unbound_text_fields: Vec::new(),
|
|
|
|
stream_manager: StreamManager::new(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 00:56:57 +00:00
|
|
|
/// Builds the player, wiring up the backends and configuring the specified settings.
|
2022-04-29 03:43:36 +00:00
|
|
|
pub fn build(self) -> Arc<Mutex<Player>> {
|
2022-04-26 00:56:57 +00:00
|
|
|
use crate::backend::*;
|
2022-08-25 22:20:47 +00:00
|
|
|
use ruffle_video::null;
|
2022-04-26 00:56:57 +00:00
|
|
|
let audio = self
|
|
|
|
.audio
|
|
|
|
.unwrap_or_else(|| Box::new(audio::NullAudioBackend::new()));
|
|
|
|
let log = self
|
|
|
|
.log
|
|
|
|
.unwrap_or_else(|| Box::new(log::NullLogBackend::new()));
|
|
|
|
let navigator = self
|
|
|
|
.navigator
|
|
|
|
.unwrap_or_else(|| Box::new(navigator::NullNavigatorBackend::new()));
|
2022-08-04 05:50:18 +00:00
|
|
|
let renderer = self.renderer.unwrap_or_else(|| {
|
|
|
|
Box::new(NullRenderer::new(ViewportDimensions {
|
|
|
|
width: self.viewport_width,
|
|
|
|
height: self.viewport_height,
|
|
|
|
scale_factor: self.viewport_scale_factor,
|
|
|
|
}))
|
|
|
|
});
|
2022-04-26 00:56:57 +00:00
|
|
|
let storage = self
|
|
|
|
.storage
|
|
|
|
.unwrap_or_else(|| Box::new(storage::MemoryStorageBackend::new()));
|
|
|
|
let ui = self
|
|
|
|
.ui
|
|
|
|
.unwrap_or_else(|| Box::new(ui::NullUiBackend::new()));
|
|
|
|
let video = self
|
|
|
|
.video
|
2022-08-25 22:20:47 +00:00
|
|
|
.unwrap_or_else(|| Box::new(null::NullVideoBackend::new()));
|
2022-04-26 03:08:21 +00:00
|
|
|
|
2022-12-29 21:44:20 +00:00
|
|
|
let player_version = self.player_version.unwrap_or(NEWEST_PLAYER_VERSION);
|
|
|
|
|
2022-04-26 03:35:56 +00:00
|
|
|
// Instantiate the player.
|
2022-12-29 21:44:20 +00:00
|
|
|
let fake_movie = Arc::new(SwfMovie::empty(player_version));
|
2023-04-11 10:13:56 +00:00
|
|
|
let frame_rate = self.frame_rate.unwrap_or(12.0);
|
|
|
|
let forced_frame_rate = self.frame_rate.is_some();
|
2022-04-27 01:25:08 +00:00
|
|
|
let player = Arc::new_cyclic(|self_ref| {
|
|
|
|
Mutex::new(Player {
|
|
|
|
// Backends
|
|
|
|
audio,
|
|
|
|
log,
|
|
|
|
navigator,
|
|
|
|
renderer,
|
|
|
|
storage,
|
|
|
|
ui,
|
|
|
|
video,
|
|
|
|
|
|
|
|
// SWF info
|
|
|
|
swf: fake_movie.clone(),
|
|
|
|
current_frame: None,
|
|
|
|
|
|
|
|
// Timing
|
|
|
|
frame_rate,
|
2023-04-11 10:13:56 +00:00
|
|
|
forced_frame_rate,
|
2022-01-22 23:54:00 +00:00
|
|
|
frame_phase: Default::default(),
|
2022-04-27 01:25:08 +00:00
|
|
|
frame_accumulator: 0.0,
|
|
|
|
recent_run_frame_timings: VecDeque::with_capacity(10),
|
|
|
|
start_time: Instant::now(),
|
|
|
|
time_offset: 0,
|
|
|
|
time_til_next_timer: None,
|
|
|
|
max_execution_duration: self.max_execution_duration,
|
2022-08-07 23:21:21 +00:00
|
|
|
actions_since_timeout_check: 0,
|
2022-04-27 01:25:08 +00:00
|
|
|
|
|
|
|
// Input
|
|
|
|
input: Default::default(),
|
2023-04-09 12:01:03 +00:00
|
|
|
mouse_in_stage: true,
|
2023-04-27 17:34:38 +00:00
|
|
|
mouse_position: Point::ZERO,
|
2022-04-27 01:25:08 +00:00
|
|
|
mouse_cursor: MouseCursor::Arrow,
|
|
|
|
mouse_cursor_needs_check: false,
|
|
|
|
|
|
|
|
// Misc. state
|
2022-08-11 14:55:19 +00:00
|
|
|
rng: SmallRng::seed_from_u64(get_current_date_time().timestamp_millis() as u64),
|
2023-02-09 19:06:17 +00:00
|
|
|
system: SystemProperties::new(self.sandbox_type),
|
2022-04-27 01:25:08 +00:00
|
|
|
transform_stack: TransformStack::new(),
|
|
|
|
instance_counter: 0,
|
2022-12-29 21:44:20 +00:00
|
|
|
player_version,
|
2022-04-27 01:25:08 +00:00
|
|
|
is_playing: self.autoplay,
|
|
|
|
needs_render: true,
|
|
|
|
warn_on_unsupported_content: self.warn_on_unsupported_content,
|
|
|
|
self_reference: self_ref.clone(),
|
2022-09-17 23:11:07 +00:00
|
|
|
load_behavior: self.load_behavior,
|
2022-10-03 23:48:40 +00:00
|
|
|
spoofed_url: self.spoofed_url.clone(),
|
2023-02-28 16:14:54 +00:00
|
|
|
compatibility_rules: self.compatibility_rules.clone(),
|
2023-01-31 15:45:14 +00:00
|
|
|
stub_tracker: StubCollection::new(),
|
2022-04-27 01:25:08 +00:00
|
|
|
|
|
|
|
// GC data
|
2022-08-28 16:30:20 +00:00
|
|
|
gc_arena: Rc::new(RefCell::new(GcArena::new(
|
|
|
|
ArenaParameters::default(),
|
2023-03-15 22:46:43 +00:00
|
|
|
|gc_context| {
|
|
|
|
Self::create_gc_root(
|
2022-08-28 16:30:20 +00:00
|
|
|
gc_context,
|
2023-03-15 22:46:43 +00:00
|
|
|
player_version,
|
|
|
|
self.fullscreen,
|
|
|
|
fake_movie.clone(),
|
|
|
|
)
|
2022-08-28 16:30:20 +00:00
|
|
|
},
|
|
|
|
))),
|
2022-04-27 01:25:08 +00:00
|
|
|
})
|
|
|
|
});
|
2022-04-26 03:08:21 +00:00
|
|
|
|
2022-04-27 01:25:08 +00:00
|
|
|
// Finalize configuration and load the movie.
|
|
|
|
let mut player_lock = player.lock().unwrap();
|
|
|
|
player_lock.mutate_with_update_context(|context| {
|
2022-04-29 03:43:36 +00:00
|
|
|
Avm2::load_player_globals(context).expect("Unable to load AVM2 globals");
|
2022-04-26 03:08:21 +00:00
|
|
|
let stage = context.stage;
|
2023-02-04 11:55:09 +00:00
|
|
|
stage.set_scale_mode(context, self.scale_mode);
|
|
|
|
stage.set_forced_scale_mode(context, self.forced_scale_mode);
|
2022-04-26 03:08:21 +00:00
|
|
|
stage.post_instantiation(context, None, Instantiator::Movie, false);
|
|
|
|
stage.build_matrices(context);
|
2022-04-29 03:43:36 +00:00
|
|
|
});
|
2022-08-28 16:30:20 +00:00
|
|
|
player_lock.gc_arena.borrow().mutate(|context, root| {
|
|
|
|
let call_stack = root.data.read().avm2.call_stack();
|
|
|
|
root.callstack.write(context).avm2 = Some(call_stack);
|
|
|
|
});
|
2022-04-27 01:25:08 +00:00
|
|
|
player_lock.audio.set_frame_rate(frame_rate);
|
2022-04-26 03:35:56 +00:00
|
|
|
player_lock.set_letterbox(self.letterbox);
|
2023-02-03 15:53:24 +00:00
|
|
|
player_lock.set_quality(self.quality);
|
2022-08-04 05:50:18 +00:00
|
|
|
player_lock.set_viewport_dimensions(ViewportDimensions {
|
|
|
|
width: self.viewport_width,
|
|
|
|
height: self.viewport_height,
|
|
|
|
scale_factor: self.viewport_scale_factor,
|
|
|
|
});
|
2022-10-03 23:48:40 +00:00
|
|
|
if let Some(mut movie) = self.movie {
|
|
|
|
if let Some(url) = self.spoofed_url.clone() {
|
2023-02-26 10:25:41 +00:00
|
|
|
movie.set_url(url);
|
2022-10-03 23:48:40 +00:00
|
|
|
}
|
2022-04-26 03:35:56 +00:00
|
|
|
player_lock.set_root_movie(movie);
|
|
|
|
}
|
|
|
|
drop(player_lock);
|
2022-04-29 03:43:36 +00:00
|
|
|
player
|
2022-04-26 00:56:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 03:35:56 +00:00
|
|
|
impl Default for PlayerBuilder {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(no_drop)]
|
2019-12-21 23:37:27 +00:00
|
|
|
pub struct DragObject<'gc> {
|
|
|
|
/// The display object being dragged.
|
|
|
|
pub display_object: DisplayObject<'gc>,
|
|
|
|
|
2023-04-27 19:22:48 +00:00
|
|
|
/// The last seen mouse position.
|
|
|
|
#[collect(require_static)]
|
|
|
|
pub last_mouse_position: Point<Twips>,
|
|
|
|
|
|
|
|
/// Whether the dragged object is locked to the center of the mouse position, or locked to the
|
|
|
|
/// point where the user first clicked it.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2023-04-27 19:22:48 +00:00
|
|
|
pub lock_center: bool,
|
2019-12-21 23:37:27 +00:00
|
|
|
|
|
|
|
/// The bounding rectangle where the clip will be maintained.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2023-03-03 10:54:56 +00:00
|
|
|
pub constraint: Rectangle<Twips>,
|
2019-12-21 23:37:27 +00:00
|
|
|
}
|
2023-02-17 19:04:52 +00:00
|
|
|
|
|
|
|
fn run_mouse_pick<'gc>(
|
|
|
|
context: &mut UpdateContext<'_, 'gc>,
|
|
|
|
require_button_mode: bool,
|
|
|
|
) -> Option<InteractiveObject<'gc>> {
|
|
|
|
context.stage.iter_render_list().rev().find_map(|level| {
|
|
|
|
level.as_interactive().and_then(|l| {
|
|
|
|
if context.is_action_script_3() {
|
|
|
|
let mut res = None;
|
|
|
|
if let Avm2MousePick::Hit(target) =
|
|
|
|
l.mouse_pick_avm2(context, *context.mouse_position, require_button_mode)
|
|
|
|
{
|
|
|
|
// Flash Player appears to never target events at the root object
|
|
|
|
if !target.as_displayobject().is_root() {
|
|
|
|
res = Some(target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res
|
|
|
|
} else {
|
|
|
|
l.mouse_pick_avm1(context, *context.mouse_position, require_button_mode)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|