diff --git a/core/src/backend.rs b/core/src/backend.rs index d7e43c85f1b5..f1d8a36b80e6 100644 --- a/core/src/backend.rs +++ b/core/src/backend.rs @@ -5,3 +5,4 @@ pub mod log; pub mod navigator; pub mod render; pub mod storage; +pub mod ui; diff --git a/core/src/backend/ui.rs b/core/src/backend/ui.rs new file mode 100644 index 000000000000..e4432a9475a5 --- /dev/null +++ b/core/src/backend/ui.rs @@ -0,0 +1,22 @@ +pub trait UiBackend { + fn message(&self, message: &str); +} + +/// UiBackend that does mostly nothing +pub struct NullUiBackend {} + +impl NullUiBackend { + pub fn new() -> Self { + Self {} + } +} + +impl UiBackend for NullUiBackend { + fn message(&self, _message: &str) {} +} + +impl Default for NullUiBackend { + fn default() -> Self { + NullUiBackend::new() + } +} diff --git a/core/src/player.rs b/core/src/player.rs index 506fe8399c2d..f461869467c3 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -9,7 +9,7 @@ use crate::backend::locale::LocaleBackend; use crate::backend::navigator::{NavigatorBackend, RequestOptions}; use crate::backend::storage::StorageBackend; use crate::backend::{ - audio::AudioBackend, log::LogBackend, render::Letterbox, render::RenderBackend, + audio::AudioBackend, log::LogBackend, render::Letterbox, render::RenderBackend, ui::UiBackend, }; use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext}; use crate::display_object::{EditText, MorphShape, MovieClip}; @@ -23,7 +23,7 @@ use crate::prelude::*; use crate::property_map::PropertyMap; use crate::tag_utils::SwfMovie; use crate::transform::TransformStack; -use crate::vminterface::Instantiator; +use crate::vminterface::{AvmType, Instantiator}; use enumset::EnumSet; use gc_arena::{make_arena, ArenaParameters, Collect, GcCell}; use instant::Instant; @@ -134,6 +134,7 @@ type Input = Box; type Storage = Box; type Locale = Box; type Log = Box; +type UI = Box; pub struct Player { /// The version of the player we're emulating. @@ -159,6 +160,7 @@ pub struct Player { input: Input, locale: Locale, log: Log, + pub user_interface: UI, transform_stack: TransformStack, view_matrix: Matrix, inverse_view_matrix: Matrix, @@ -205,6 +207,7 @@ pub struct Player { self_reference: Option>>, } +#[allow(clippy::too_many_arguments)] impl Player { pub fn new( renderer: Renderer, @@ -214,6 +217,7 @@ impl Player { storage: Storage, locale: Locale, log: Log, + user_interface: UI, ) -> Result>, Error> { let fake_movie = Arc::new(SwfMovie::empty(NEWEST_PLAYER_VERSION)); let movie_width = 550; @@ -280,6 +284,7 @@ impl Player { input, locale, log, + user_interface, self_reference: None, system: SystemProperties::default(), instance_counter: 0, @@ -305,10 +310,10 @@ impl Player { player.build_matrices(); player.audio.set_frame_rate(frame_rate); - let player_box = Arc::new(Mutex::new(player)); let mut player_lock = player_box.lock().unwrap(); player_lock.self_reference = Some(Arc::downgrade(&player_box)); + std::mem::drop(player_lock); Ok(player_box) @@ -624,7 +629,7 @@ impl Player { }); } - // Propagte clip events. + // Propagate clip events. self.mutate_with_update_context(|context| { let (clip_event, listener) = match event { PlayerEvent::KeyDown { .. } => { @@ -799,6 +804,7 @@ impl Player { /// This should only be called once. Further movie loads should preload the /// specific `MovieClip` referenced. fn preload(&mut self) { + let mut is_action_script_3 = false; self.mutate_with_update_context(|context| { let mut morph_shapes = fnv::FnvHashMap::default(); let root = *context.levels.get(&0).expect("root level"); @@ -806,15 +812,20 @@ impl Player { .unwrap() .preload(context, &mut morph_shapes); + let lib = context + .library + .library_for_movie_mut(root.as_movie_clip().unwrap().movie().unwrap()); + + is_action_script_3 = lib.avm_type() == AvmType::Avm2; // Finalize morph shapes. for (id, static_data) in morph_shapes { let morph_shape = MorphShape::new(context.gc_context, static_data); - context - .library - .library_for_movie_mut(root.as_movie_clip().unwrap().movie().unwrap()) - .register_character(id, crate::character::Character::MorphShape(morph_shape)); + lib.register_character(id, crate::character::Character::MorphShape(morph_shape)); } }); + if is_action_script_3 { + self.user_interface.message("This SWF contains ActionScript 3 which is not yet supported by Ruffle. The movie may not work as intended."); + } } pub fn run_frame(&mut self) { diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index f8f9744d9d2f..5bf844be7f2f 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -7,6 +7,7 @@ use ruffle_core::backend::locale::NullLocaleBackend; use ruffle_core::backend::log::LogBackend; use ruffle_core::backend::navigator::{NullExecutor, NullNavigatorBackend}; use ruffle_core::backend::storage::MemoryStorageBackend; +use ruffle_core::backend::ui::NullUiBackend; use ruffle_core::backend::{ audio::NullAudioBackend, input::NullInputBackend, render::NullRenderer, }; @@ -694,6 +695,7 @@ fn run_swf( Box::new(MemoryStorageBackend::default()), Box::new(NullLocaleBackend::new()), Box::new(TestLogBackend::new(trace_output.clone())), + Box::new(NullUiBackend::new()), )?; player.lock().unwrap().set_root_movie(Arc::new(movie)); player diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 58ade25844e9..c04963871852 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -8,6 +8,7 @@ mod locale; mod navigator; mod storage; mod task; +mod ui; use crate::custom_event::RuffleEvent; use crate::executor::GlutinAsyncExecutor; @@ -215,6 +216,7 @@ fn run_player(opt: Opt) -> Result<(), Box> { )); //TODO: actually implement this backend type let input = Box::new(input::WinitInputBackend::new(window.clone())); let storage = Box::new(DiskStorageBackend::new()); + let user_interface = Box::new(ui::DesktopUiBackend::new()); let locale = Box::new(locale::DesktopLocaleBackend::new()); let player = Player::new( renderer, @@ -224,6 +226,7 @@ fn run_player(opt: Opt) -> Result<(), Box> { storage, locale, Box::new(NullLogBackend::new()), + user_interface, )?; player.lock().unwrap().set_root_movie(Arc::new(movie)); player.lock().unwrap().set_is_playing(true); // Desktop player will auto-play. diff --git a/desktop/src/ui.rs b/desktop/src/ui.rs new file mode 100644 index 000000000000..b90b9f7a6b59 --- /dev/null +++ b/desktop/src/ui.rs @@ -0,0 +1,23 @@ +use tinyfiledialogs::{message_box_ok, MessageBoxIcon}; + +use ruffle_core::backend::ui::UiBackend; + +pub struct DesktopUiBackend {} + +impl DesktopUiBackend { + pub fn new() -> Self { + Self {} + } +} + +impl UiBackend for DesktopUiBackend { + fn message(&self, message: &str) { + message_box_ok("Ruffle", message, MessageBoxIcon::Info) + } +} + +impl Default for DesktopUiBackend { + fn default() -> Self { + DesktopUiBackend::new() + } +} diff --git a/exporter/src/main.rs b/exporter/src/main.rs index 2194ddb66595..fed6c1719d5a 100644 --- a/exporter/src/main.rs +++ b/exporter/src/main.rs @@ -8,6 +8,7 @@ use ruffle_core::backend::locale::NullLocaleBackend; use ruffle_core::backend::log::NullLogBackend; use ruffle_core::backend::navigator::NullNavigatorBackend; use ruffle_core::backend::storage::MemoryStorageBackend; +use ruffle_core::backend::ui::NullUiBackend; use ruffle_core::tag_utils::SwfMovie; use ruffle_core::Player; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; @@ -111,6 +112,7 @@ fn take_screenshot( Box::new(MemoryStorageBackend::default()), Box::new(NullLocaleBackend::new()), Box::new(NullLogBackend::new()), + Box::new(NullUiBackend::new()), )?; player diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index 658d0d0ed388..c2d3c6f49ccd 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -790,6 +790,25 @@ export class RufflePlayer extends HTMLElement { } } + displayMessage(message: string): void { + // Show a dismissible message in front of the player + const div = document.createElement("div"); + div.id = "message_overlay"; + div.innerHTML = `
+
+

${message}

+
+
+ +
`; + this.container.prepend(div); + (( + this.container.querySelector("#continue-btn") + )).onclick = () => { + div.remove(); + }; + } + protected debugPlayerInfo(): string { return `Allows script access: ${this.allowScriptAccess}\n`; } diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index 86bfce09d3dc..1391fa8a6994 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -25,7 +25,8 @@ ruffleShadowTemplate.innerHTML = ` #play_button, #unmute_overlay, #unmute_overlay .background, - #panic { + #panic, + #message_overlay { width: inherit; height: inherit; } @@ -122,6 +123,39 @@ ruffleShadowTemplate.innerHTML = ` padding: 10px 50px; } + #message_overlay { + position: absolute; + background-color: #37528C; + color: #FFAD33; + opacity: 1.0; + z-index: 2; + text-align: center; + } + + #message_overlay .message { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + padding: 20px; + transform: translate(-50%, -50%); + } + + #continue-btn { + cursor: pointer; + background-color: #37528C; + color: #FFAD33; + border: 2px solid #FFAD33; + font-weight: bold; + font-size: 20px; + border-radius: 20px; + padding: 10px; + } + + #continue-btn:hover { + background-color: rgba(255, 255, 255, 0.3); + } + #right_click_menu { color: #FFAD33; background-color: #37528c; diff --git a/web/src/lib.rs b/web/src/lib.rs index 07ecc4ec75b1..74571610dfc9 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -7,6 +7,7 @@ mod locale; mod log_adapter; mod navigator; mod storage; +mod ui; use crate::log_adapter::WebLogBackend; use crate::storage::LocalStorageBackend; @@ -96,6 +97,9 @@ extern "C" { #[wasm_bindgen(method)] fn panic(this: &JavascriptPlayer, error: &JsError); + + #[wasm_bindgen(method, js_name = "displayMessage")] + fn display_message(this: &JavascriptPlayer, message: &str); } struct JavascriptInterface { @@ -317,7 +321,7 @@ impl Ruffle { let trace_observer = Arc::new(RefCell::new(JsValue::UNDEFINED)); let log = Box::new(WebLogBackend::new(trace_observer.clone())); - + let user_interface = Box::new(ui::WebUiBackend::new(js_player.clone())); let core = ruffle_core::Player::new( renderer, audio, @@ -326,6 +330,7 @@ impl Ruffle { local_storage, locale, log, + user_interface, )?; // Create instance. diff --git a/web/src/ui.rs b/web/src/ui.rs new file mode 100644 index 000000000000..47b6c85d5f40 --- /dev/null +++ b/web/src/ui.rs @@ -0,0 +1,18 @@ +use super::JavascriptPlayer; +use ruffle_core::backend::ui::UiBackend; + +pub struct WebUiBackend { + js_player: JavascriptPlayer, +} + +impl WebUiBackend { + pub fn new(js_player: JavascriptPlayer) -> Self { + Self { js_player } + } +} + +impl UiBackend for WebUiBackend { + fn message(&self, message: &str) { + self.js_player.display_message(message); + } +}