web: Support wmode parameter
This commit is contained in:
parent
8131a4f3cc
commit
3305ac69c4
|
@ -46,7 +46,7 @@ pub use graphic::Graphic;
|
|||
pub use interactive::{InteractiveObject, TInteractiveObject};
|
||||
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
||||
pub use movie_clip::{MovieClip, Scene};
|
||||
pub use stage::{Stage, StageAlign, StageDisplayState, StageQuality, StageScaleMode};
|
||||
pub use stage::{Stage, StageAlign, StageDisplayState, StageQuality, StageScaleMode, WindowMode};
|
||||
pub use text::Text;
|
||||
pub use video::Video;
|
||||
|
||||
|
|
|
@ -95,6 +95,11 @@ pub struct StageData<'gc> {
|
|||
/// The bounds of the current viewport in twips, used for culling.
|
||||
view_bounds: BoundingBox,
|
||||
|
||||
/// The window mode of the viewport.
|
||||
///
|
||||
/// Only used on web to control how the Flash content layers with other content on the page.
|
||||
window_mode: WindowMode,
|
||||
|
||||
/// Whether to show default context menu items
|
||||
show_menu: bool,
|
||||
|
||||
|
@ -121,6 +126,7 @@ impl<'gc> Stage<'gc> {
|
|||
viewport_size: (width, height),
|
||||
viewport_scale_factor: 1.0,
|
||||
view_bounds: Default::default(),
|
||||
window_mode: Default::default(),
|
||||
show_menu: true,
|
||||
avm2_object: Avm2ScriptObject::bare_object(gc_context),
|
||||
},
|
||||
|
@ -319,6 +325,22 @@ impl<'gc> Stage<'gc> {
|
|||
self.build_matrices(context);
|
||||
}
|
||||
|
||||
/// Get the stage mode.
|
||||
/// This controls how the content layers with other content on the page.
|
||||
/// Only used on web.
|
||||
pub fn window_mode(self) -> WindowMode {
|
||||
self.0.read().window_mode
|
||||
}
|
||||
|
||||
/// Sets the window mode.
|
||||
pub fn set_window_mode(
|
||||
self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
window_mode: WindowMode,
|
||||
) {
|
||||
self.0.write(context.gc_context).window_mode = window_mode;
|
||||
}
|
||||
|
||||
pub fn view_bounds(self) -> BoundingBox {
|
||||
self.0.read().view_bounds.clone()
|
||||
}
|
||||
|
@ -334,12 +356,13 @@ impl<'gc> Stage<'gc> {
|
|||
|
||||
/// Determine if we should letterbox the stage content.
|
||||
fn should_letterbox(self) -> bool {
|
||||
// Only enable letterbox is the default `ShowAll` scale mode.
|
||||
// Only enable letterbox in the default `ShowAll` scale mode.
|
||||
// If content changes the scale mode or alignment, it signals that it is size-aware.
|
||||
// For example, `NoScale` is used to make responsive layouts; don't letterbox over it.
|
||||
let stage = self.0.read();
|
||||
stage.scale_mode == StageScaleMode::ShowAll
|
||||
&& stage.align.is_empty()
|
||||
&& stage.window_mode != WindowMode::Transparent
|
||||
&& (stage.letterbox == Letterbox::On
|
||||
|| (stage.letterbox == Letterbox::Fullscreen && self.is_fullscreen()))
|
||||
}
|
||||
|
@ -673,9 +696,13 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
|
|||
}
|
||||
|
||||
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
|
||||
let background_color = self
|
||||
.background_color()
|
||||
.unwrap_or_else(|| Color::from_rgb(0xffffff, 255));
|
||||
let background_color =
|
||||
if self.window_mode() != WindowMode::Transparent || self.is_fullscreen() {
|
||||
self.background_color()
|
||||
.unwrap_or_else(|| Color::from_rgb(0xffffff, 255))
|
||||
} else {
|
||||
Color::from_rgba(0)
|
||||
};
|
||||
|
||||
context.renderer.begin_frame(background_color);
|
||||
|
||||
|
@ -1060,3 +1087,73 @@ impl FromWStr for StageQuality {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The window mode of the Ruffle player.
|
||||
///
|
||||
/// This setting controls how the Ruffle container is layered and rendered with other content on
|
||||
/// the page. This setting is only used on web.
|
||||
///
|
||||
/// [Apply OBJECT and EMBED tag attributes in Adobe Flash Professional](https://helpx.adobe.com/flash/kb/flash-object-embed-tag-attributes.html)
|
||||
#[derive(Clone, Collect, Copy, Debug, Eq, PartialEq)]
|
||||
#[collect(require_static)]
|
||||
pub enum WindowMode {
|
||||
/// The Flash content is rendered in its own window and layering is done with the browser's
|
||||
/// default behavior.
|
||||
///
|
||||
/// In Ruffle, this mode functions like `WindowMode::Opaque` and will layer the Flash content
|
||||
/// together with other HTML elements.
|
||||
Window,
|
||||
|
||||
/// The Flash content is layered together with other HTML elements, and the stage color is
|
||||
/// opaque. Content can render above or below Ruffle based on CSS rendering order.
|
||||
Opaque,
|
||||
|
||||
/// The Flash content is layered together with other HTML elements, and the stage color is
|
||||
/// transparent. Content beneath Ruffle will be visible through transparent areas.
|
||||
Transparent,
|
||||
|
||||
/// Request compositing with hardware acceleration when possible.
|
||||
///
|
||||
/// This mode has no effect in Ruffle and will function like `WindowMode::Opaque`.
|
||||
Gpu,
|
||||
|
||||
/// Request a direct rendering path, bypassing browser compositing when possible.
|
||||
///
|
||||
/// This mode has no effect in Ruffle and will function like `WindowMode::Opaque`.
|
||||
Direct,
|
||||
}
|
||||
|
||||
impl Default for WindowMode {
|
||||
fn default() -> WindowMode {
|
||||
WindowMode::Window
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for WindowMode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let s = match *self {
|
||||
WindowMode::Window => "window",
|
||||
WindowMode::Opaque => "opaque",
|
||||
WindowMode::Transparent => "transparent",
|
||||
WindowMode::Direct => "direct",
|
||||
WindowMode::Gpu => "gpu",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for WindowMode {
|
||||
type Err = ParseEnumError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let window_mode = match s.to_ascii_lowercase().as_str() {
|
||||
"window" => WindowMode::Window,
|
||||
"opaque" => WindowMode::Opaque,
|
||||
"transparent" => WindowMode::Transparent,
|
||||
"direct" => WindowMode::Direct,
|
||||
"gpu" => WindowMode::Gpu,
|
||||
_ => return Err(ParseEnumError),
|
||||
};
|
||||
Ok(window_mode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext};
|
|||
use crate::context_menu::{ContextMenuCallback, ContextMenuItem, ContextMenuState};
|
||||
use crate::display_object::{
|
||||
EditText, InteractiveObject, MovieClip, Stage, StageAlign, StageDisplayState, StageQuality,
|
||||
StageScaleMode, TInteractiveObject,
|
||||
StageScaleMode, TInteractiveObject, WindowMode,
|
||||
};
|
||||
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, MouseButton, PlayerEvent};
|
||||
use crate::external::Value as ExternalValue;
|
||||
|
@ -813,6 +813,15 @@ impl Player {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn set_window_mode(&mut self, scale_mode: &str) {
|
||||
self.mutate_with_update_context(|context| {
|
||||
let stage = context.stage;
|
||||
if let Ok(window_mode) = WindowMode::from_str(scale_mode) {
|
||||
stage.set_window_mode(context, window_mode);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Handle an event sent into the player from the external windowing system
|
||||
/// or an HTML element.
|
||||
///
|
||||
|
|
|
@ -230,7 +230,10 @@ impl BitmapData {
|
|||
}
|
||||
|
||||
impl WebCanvasRenderBackend {
|
||||
pub fn new(canvas: &HtmlCanvasElement) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
pub fn new(
|
||||
canvas: &HtmlCanvasElement,
|
||||
is_transparent: bool,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
// Request the CanvasRenderingContext2d.
|
||||
// Disable alpha for possible speedup.
|
||||
// TODO: Allow user to enable transparent background (transparent wmode in legacy Flash).
|
||||
|
@ -238,7 +241,11 @@ impl WebCanvasRenderBackend {
|
|||
let _ = js_sys::Reflect::set(
|
||||
&context_options,
|
||||
&"alpha".into(),
|
||||
&wasm_bindgen::JsValue::FALSE,
|
||||
&if is_transparent {
|
||||
wasm_bindgen::JsValue::TRUE
|
||||
} else {
|
||||
wasm_bindgen::JsValue::FALSE
|
||||
},
|
||||
);
|
||||
let context: CanvasRenderingContext2d = canvas
|
||||
.get_context_with_context_options("2d", &context_options)
|
||||
|
@ -636,10 +643,17 @@ impl RenderBackend for WebCanvasRenderBackend {
|
|||
let width = self.canvas.width();
|
||||
let height = self.canvas.height();
|
||||
|
||||
let color = format!("rgb({}, {}, {})", clear.r, clear.g, clear.b);
|
||||
if clear.a > 0 {
|
||||
let color = format!("rgba({}, {}, {}, {})", clear.r, clear.g, clear.b, clear.a);
|
||||
self.context.set_fill_style(&color.into());
|
||||
let _ = self.context.set_global_composite_operation("copy");
|
||||
self.context
|
||||
.fill_rect(0.0, 0.0, width.into(), height.into());
|
||||
let _ = self.context.set_global_composite_operation("source-over");
|
||||
} else {
|
||||
self.context
|
||||
.clear_rect(0.0, 0.0, width.into(), height.into());
|
||||
}
|
||||
|
||||
self.deactivating_mask = false;
|
||||
}
|
||||
|
|
|
@ -76,6 +76,44 @@ export const enum LogLevel {
|
|||
Trace = "trace",
|
||||
}
|
||||
|
||||
/**
|
||||
* The window mode of a Ruffle player.
|
||||
*/
|
||||
export const enum WindowMode {
|
||||
/*
|
||||
* The Flash content is rendered in its own window and layering is done with the browser's
|
||||
* default behavior.
|
||||
*
|
||||
* In Ruffle, this mode functions like `WindowMode::Opaque` and will layer the Flash content
|
||||
* together with other HTML elements.
|
||||
*/
|
||||
Window = "window",
|
||||
|
||||
/*
|
||||
* The Flash content is layered together with other HTML elements, and the stage color is
|
||||
* opaque. Content can render above or below Ruffle based on CSS rendering order.
|
||||
*/
|
||||
Opaque = "opaque",
|
||||
|
||||
/*
|
||||
* The Flash content is layered together with other HTML elements, and the SWF stage color is
|
||||
* transparent. Content beneath Ruffle will be visible through transparent areas.
|
||||
*/
|
||||
Transparent = "transparent",
|
||||
|
||||
/*
|
||||
* Request compositing with hardware acceleration when possible.
|
||||
* This mode has no effect in Ruffle and will function like `WindowMode.Opaque`.
|
||||
*/
|
||||
Direct = "direct",
|
||||
|
||||
/*
|
||||
* Request a direct rendering path, bypassing browser compositing when possible.
|
||||
* This mode has no effect in Ruffle and will function like `WindowMode::Opaque`.
|
||||
*/
|
||||
Gpu = "gpu",
|
||||
}
|
||||
|
||||
/**
|
||||
* Any options used for loading a movie.
|
||||
*/
|
||||
|
@ -227,6 +265,15 @@ export interface BaseLoadOptions {
|
|||
* @default "showAll"
|
||||
*/
|
||||
scale?: string;
|
||||
|
||||
/**
|
||||
* The window mode of the Ruffle player.
|
||||
*
|
||||
* This setting controls how the Ruffle container is layered and rendered with other content on the page.
|
||||
*
|
||||
* @default WindowMode.Window
|
||||
*/
|
||||
wmode?: WindowMode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
workaroundYoutubeMixedContent,
|
||||
RufflePlayer,
|
||||
} from "./ruffle-player";
|
||||
import { WindowMode } from "./load-options";
|
||||
import { registerElement } from "./register-element";
|
||||
|
||||
/**
|
||||
|
@ -61,6 +62,9 @@ export class RuffleEmbed extends RufflePlayer {
|
|||
this.attributes.getNamedItem("quality")?.value ?? "high",
|
||||
scale:
|
||||
this.attributes.getNamedItem("scale")?.value ?? "showAll",
|
||||
wmode:
|
||||
(this.attributes.getNamedItem("wmode")
|
||||
?.value as WindowMode) ?? WindowMode.Window,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
RufflePlayer,
|
||||
} from "./ruffle-player";
|
||||
import { registerElement } from "./register-element";
|
||||
import { URLLoadOptions } from "./load-options";
|
||||
import { URLLoadOptions, WindowMode } from "./load-options";
|
||||
import { RuffleEmbed } from "./ruffle-embed";
|
||||
|
||||
/**
|
||||
|
@ -126,6 +126,7 @@ export class RuffleObject extends RufflePlayer {
|
|||
const salign = findCaseInsensitive(this.params, "salign", "");
|
||||
const quality = findCaseInsensitive(this.params, "quality", "high");
|
||||
const scale = findCaseInsensitive(this.params, "scale", "showAll");
|
||||
const wmode = findCaseInsensitive(this.params, "wmode", "window");
|
||||
|
||||
if (url) {
|
||||
const options: URLLoadOptions = { url };
|
||||
|
@ -152,6 +153,9 @@ export class RuffleObject extends RufflePlayer {
|
|||
if (scale) {
|
||||
options.scale = scale;
|
||||
}
|
||||
if (wmode) {
|
||||
options.wmode = wmode as WindowMode;
|
||||
}
|
||||
|
||||
// Kick off the SWF download.
|
||||
this.load(options);
|
||||
|
|
|
@ -145,6 +145,8 @@ pub struct Config {
|
|||
|
||||
scale: Option<String>,
|
||||
|
||||
wmode: Option<String>,
|
||||
|
||||
#[serde(rename = "warnOnUnsupportedContent")]
|
||||
warn_on_unsupported_content: bool,
|
||||
|
||||
|
@ -163,6 +165,7 @@ impl Default for Config {
|
|||
salign: Some("".to_owned()),
|
||||
quality: Some("high".to_owned()),
|
||||
scale: Some("showAll".to_owned()),
|
||||
wmode: Some("opaque".to_owned()),
|
||||
background_color: Default::default(),
|
||||
letterbox: Default::default(),
|
||||
upgrade_to_https: true,
|
||||
|
@ -465,7 +468,8 @@ impl Ruffle {
|
|||
let window = web_sys::window().ok_or("Expected window")?;
|
||||
let document = window.document().ok_or("Expected document")?;
|
||||
|
||||
let (canvas, renderer) = create_renderer(&document).await?;
|
||||
let (canvas, renderer) = create_renderer(&document, &config).await?;
|
||||
|
||||
parent
|
||||
.append_child(&canvas.clone().into())
|
||||
.into_js_result()?;
|
||||
|
@ -507,6 +511,7 @@ impl Ruffle {
|
|||
core.set_stage_align(config.salign.as_deref().unwrap_or(""));
|
||||
core.set_quality(config.quality.as_deref().unwrap_or("high"));
|
||||
core.set_scale_mode(config.scale.as_deref().unwrap_or("showAll"));
|
||||
core.set_window_mode(config.wmode.as_deref().unwrap_or("window"));
|
||||
|
||||
// Create the external interface.
|
||||
if allow_script_access {
|
||||
|
@ -1204,10 +1209,13 @@ fn external_to_js_value(external: ExternalValue) -> JsValue {
|
|||
|
||||
async fn create_renderer(
|
||||
document: &web_sys::Document,
|
||||
config: &Config,
|
||||
) -> Result<(HtmlCanvasElement, Box<dyn RenderBackend>), Box<dyn Error>> {
|
||||
#[cfg(not(any(feature = "canvas", feature = "webgl", feature = "wgpu")))]
|
||||
std::compile_error!("You must enable one of the render backend features (e.g., webgl).");
|
||||
|
||||
let _is_transparent = config.wmode.as_deref() == Some("transparent");
|
||||
|
||||
// Try to create a backend, falling through to the next backend on failure.
|
||||
// We must recreate the canvas each attempt, as only a single context may be created per canvas
|
||||
// with `getContext`.
|
||||
|
@ -1244,7 +1252,7 @@ async fn create_renderer(
|
|||
.into_js_result()?
|
||||
.dyn_into()
|
||||
.map_err(|_| "Expected HtmlCanvasElement")?;
|
||||
match ruffle_render_webgl::WebGlRenderBackend::new(&canvas, true) {
|
||||
match ruffle_render_webgl::WebGlRenderBackend::new(&canvas, _is_transparent) {
|
||||
Ok(renderer) => return Ok((canvas, Box::new(renderer))),
|
||||
Err(error) => log::error!("Error creating WebGL renderer: {}", error),
|
||||
}
|
||||
|
@ -1258,7 +1266,7 @@ async fn create_renderer(
|
|||
.into_js_result()?
|
||||
.dyn_into()
|
||||
.map_err(|_| "Expected HtmlCanvasElement")?;
|
||||
match ruffle_render_canvas::WebCanvasRenderBackend::new(&canvas) {
|
||||
match ruffle_render_canvas::WebCanvasRenderBackend::new(&canvas, _is_transparent) {
|
||||
Ok(renderer) => return Ok((canvas, Box::new(renderer))),
|
||||
Err(error) => log::error!("Error creating canvas renderer: {}", error),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue