This PR implements the `Loader.load` method, as well as
the associated `LoaderInfo` properties and events.
We can now load in an external AVM2 SWf: it will be added
as a child of `Loader` object, and will render properly
to the screen.
Limitations:
* The only supported `URLRequest` property is `url`
* `LoaderContext` is not supported at all - we always use the default
behavior
* Only `Loader.load` is implemented - we do not yet support unloading.
* We fire a plain 'Event' for the 'progress' event, instead of using
the (not yet implemented) 'ProgressEvent' class
The main changes in this PR are:
* The AVM2 `Loader` class now has an associated display object,
`LoaderDisplay`. This is basically a stub, and just renders
its single child (if it exists).
* `LoaderStream::Stage` is renamed to `LoaderStream::NotYetLoaded`.
This is used for both the `Stage` and an 'uninitialized'
`Loader.contentLoaderInfo`. In both cases, certain properties throw
errors, while others return actual values.
* The rust `Loader` manager now handles both AVM1 and AVM2 movie loads.
Each render backend keeps track of a stack of BlenModes,
which are pushed and popped by 'core' as we render objects
in the displaay tree. For now, I've just implemented BlendMode.ADD,
which maps directly onto blend mode supported by each backend.
All other blend modes (besides 'NORMAL') will produce a warning
when we try to render using them. This may produce a very large amount
of log output, but it's simpler than emitting each warning only once,
and will help to point developers in the right direction when they
get otherwise inexplicable rendering issues (due to a blend mode
not being implemented).
The wgpu implementation is by far the most complicated, as we need
to construct a `RenderPipeline` for each possible
`(BlendMode, MaskState)`. I haven't been able to find any documentation
about the maximum supported number of (simultaneous) WebGPU render
pipelines - if this becomes an issue, we may need to register them
on-demand when a particular blend mode is requested.
This PR implements the 'DisplayObject.transform' getters/setters,
and most of the getters/setters in the `Transform` class
From testing in FP, it appears that each call to the
'DisplayObject.transform' property produces a new
'Transform' instance, which is permanently tied to the
owner 'DisplayObject'. All of the getters/setters in
`Transform` operate directly on owner `DisplayObject`.
However, note that the `Matrix` and `ColorTransform`
valuse *produced* the getter are plain ActionScript objects,
and have no further tie to the `DisplayObject`.
Using the `DisplayObject.transform` setter results in
values being *copied* from the input `Transform` object.
The input object retains its original owner `DisplayObject`.
Not implemented:
* Transform.concatenatedColorTransform
* Transform.pixelBounds
When a DisplayObject is not a descendant of the stage,
the `concatenatedMatrix` property produces a bizarre matrix:
a scale matrix that the depends on the global state quality.
Any DisplayObject that *is* a descendant of the stage has
a `concatenatedMatrix` that does not depend on the stage quality.
I'm not sure why the behavior occurs - for now, I just manually
mimic the values prdduced by FP. However, these values may indicate
that we need to do some internal scaling based on stage quality values,
and then 'undo' this in certain circumstances when constructing
an ActionScript matrix.
Unfortunately, some of the computed 'concatenatedMatrix' values
are off by f32::EPSILON. This is likely due to us storing some
internal values in pixels rather than twips (the rounding introduced
by round-trip twips conversions could cause this slight difference0.
For now, I've opted to mark these tests as 'approximate'.
To support this, I've extended our test framework to support providing
a regex that matches floating-point values in the output. This allows
us to print out 'Matrix.toString()' and still perform approximate
comparisons between strings of the format
'(a=0, b=0, c=0, d=0, tx=0, ty=0)'
Previously, we would create a fresh `LoaderInfo` object each
time the `loaderInfo` property was accessed. However, users can
add event handlers to a `LoaderInfo`, so we need to create and
store exactly one `LoaderInfo` object per movie (and stage).
To verify that we're correctly handling the storage of `LoaderInfo`,
I've implemented firing the "init" event. This required a new
`on_frame_exit` hook, so that we can properly fire the "init"
event after the "exitFrame" for the initial frame but before
the "enterFrame" of the next frame.
The fix in #5218 wasn't sufficient; 30-bit arithmetic should be used
along all the way when calculating an effective sound transform.
For example, a sound transform composited by volumes `-0x80000000` and
`25` should end up as effectively 0, whereas previously it would have
been calculated as `-0x80000000 * 25 / 100 = -0x20000000`, which is a
30-bit integer that hasn't been truncated.
Fixes#5655.
This also necessitated removing the `impl_display_object` family of macros, as you cannot name a field of a field in a macro expression. I tried. So instead I've reverted to standard default method inheritance, in the same way we did with AVM2 objects.
`handle_clip_event` is now a default trait method that calls three methods in order:
* `filter_clip_event`, to determine which events that either this object or it's children may handle
* `propagate_to_children`, to check if any children of this object want to handle an event. (This also includes AVM2 button states, which are not technically "children" in the usual sense...)
* `event_dispatch`, which does the actual "object reacts to an event" bit if no child handles the object.
These roughly correspond to phases of existing event-handling objects pre-`InteractiveObject`.
* Rename movie_clip::ClipAction to movie_clip::ClipEventHandler.
* Store the swf::ClipEventFlag event flags that trigger the event
directly in the event handler. Previously we split up any event
that had multiple event flags into separate events. Now these
can be kept as a single event.
* Remove `MovieClip::has_button_event`, and instead store the
union of all event flags in `MovieClip::clip_event_flags`. This
will be useful for other cases in the future.
A `PlaceObjectAction::Replace` signals that a shape should
be swapped with a different shape. Previously we instantiated a
completely new `Graphic`, but this is incorrect; instead the
underlying shape handle should be swapped out, but the outer object
remains. This is visible in AVM2 where you can access `Shape` as
a normal display object.
Matrices in an SWF file store their scale/skew components in
in 16.16 format (fbits).
Split `ruffle_core::Matrix` and `swf::Matrix`. `swf::Matrix` now
stores its data as `Fixed16` instead of immediately converting to
`f32`.
Move `MovieClip::is_swf` flag to `DisplayObject::is_root`, and use
this flag to handle the behavior of `DisplayObject.root` crawling
upwards until it hits a top-most loaded SWF/Bitmap.
Simplify `root` and `stage` so that they don't have to consider
buttons. Instead, do some trickery to ensure the button's states
see the proper values of `parent`, `root`, and `stage` during
construction.
`parent` is now a bare accessor, `avm1_parent` returns what AVM1 thinks the parent should be, and `avm2_parent` returns what AVM2 thinks the parent should be.
Use the proper types for ColorTransform:
* Fixed8 (8.8) format for multiplicative component
* i16 format for additive component
This matches the behavior of Flash (for example, alpha only changes
in units of 1/256).
If a child clip is named `_level0`, accessing `_level0` should
return the level and not the child clip.
Move `DisplayObject::get_level_by_path` to `StageObject`, and
change it to return an `Option<Value>`, and return
`Some(Value::Undefined)` if the path is a valid level path but
the level is not occupied. This causes get/set of `_levelN` to
be swallowed, even if the level isn't populated.
This appears to only be in use for AVM2. Objects placed on a given frame are constructed before anything else happens with it's parent - even it's constructor being called. This involves splitting AVM2 up into a bunch of steps that really don't make sense for AVM1 content. Hence, `construct_frame` is a no-op for AVM1 and pre-running the first frame when instantiated is AVM1 exclusive now.
Since 19034b7, clip event scripts are returned as slices from the
SWF. This caused a panic when a movie was loaded into a clip,
because the loaded clip's `movie` would be used as the source for
clip events. However, clip events are placed by the parent's
PlaceObject tags, so the movie in this case should be the parent's
movie.
Fixes#2870.
The purpose of this refactor is twofold:
1. Ensure `TDisplayObjectContainer` holds all the container methods we need
2. Ensure that future adjustments to trait methods automatically apply to the display object's own use of the container, in case we want to do things in those methods that can't be done in a borrowed container
There are two places where we cannot use the new trait methods:
1. `Button.set_state` as it holds a borrow at the point we want to clear the container
2. `MovieClipData.goto_remove_object`, since it's a method on the data and thus cannot access trait methods
This particular commit generates a lot of noise as several `DisplayObject` methods were incorrectly marked as non-mutating.
`ChildContainer` is responsible for maintaining child lists for all display objects that can hold children. Currently, this is just `Button` and `MovieClip` since those are the only objects in AVM1 that can have children, but this will be extended to other objects in future commits.
The number of lists managed has also increased from two to three. The execution list is unchanged save for it's migration into the `ChildContainer` struct. The render list has been split in two to support AS3. Specifically, the render list is now a `Vec`. Render children are still rendered in order but they are now referenced by AS3 `id`s rather than depths. The old `BTreeMap` that served as a render list is now the depth list and serves to maintain compatibility with SWF tags and AVM1 code that refers to things on the timeline by depth.
If a masker is placed inside a masker, the inner mask is inactive
and instead renders as normal art, masked by the outer mask. Properly
handle this case by only pushing new masks if we are not currently
drawing the mask stencil.
Maskee inside maskee still functions as expected. (i.e., a clip
using a mask is masked itself).
Change the usage of the stencil buffer to avoid running out of
stencil bits when too many nested masks are active.
This also cleans things up on wgpu which requires us to make
pipeline states in advice; now we only need a few stencil states
for masking as opposed to hundreds.
This requires the use of an intermediary enum called `AvmObject` which can hold either object representation. Currently, it's mostly just being unwrapped as AVM1 objects, which we will need to fix.
Fixes a specific pattern of preloader design where animations were handled by just making the box bigger every frame until it's 100. Of course, direct equality of f64 is a terrible idea, but it works in Flash, which apparantly must store scale in percentages. So we must, too.
There is a difference between empty/default (change value to default)
and none (don't modify), so make this explicit for some PlaceObject
parameters where it wasn't.
Fixes#1104.