This patch implements proper automatic tab ordering that
behaves exactly the same as in FP.
The automatic order depends only on the position of
the top-left highlight bound corner, referred to as `(x,y)`.
It does not depend on object's size or other corners.
The value of `6y+x` is used to order objects by it.
This means that the next object to be tabbed is the next one
that touches the line `y=-(x-p)/6` (with the smallest `p`).
When two objects have the same value of `6y+x`
(i.e. when the line touches two objects at the same time),
only one of them is included.
This behavior is similar to the naive approach of
"left-to-right, top-to-bottom", but (besides being sometimes
seen as random jumps) takes into account the fact that
the next object to the right may be positioned slightly higher.
This is especially true for objects placed by hand or objects with
different heights (as FP uses the top left corner instead of the center).
This behavior has been discovered experimentally by placing
tabbable objects randomly and bisecting one of their
coordinates to find a difference in behavior.
This lets us view a bitmap directly from the debug ui.
We cache an egui texture handle on the `BitmapData` itself,
and invalidate it whenever the BitmapData gets changed.
Loaders have a similar behavior to the main stage - if a mouse pick
would target their direct child (the root movieclip in the stage case,
and the loaded MovieClip in the Loader case), the Stage/Loader is
targeted instead.
This patch removes two methods:
* `tab_enabled_avm1`, and
* `tab_enabled_avm2_default`,
and replaces them with `tab_enabled_default`.
This refactor makes the code more readable and simple.
Buttons are always highlighted using their hit bounds.
I guess it does have some sense to it, because their bounds
usually change on hover (children are swapped out),
which would cause the automatic tab order to change during tabbing.
That could potentially create a loop in the tab ordering
(soft locking the tab).
Since highlight has 3 states now, it's better represented by
describing the state instead of having a checkbox.
A re-focus button has been added to compensate for the fact
that the highlight cannot be automatically enabled anymore.
Some button events (e.g. KeyUp/KeyDown) seem to require a state when
the highlight is not visible, but is active (i.e. a focus is selected).
That is why this patch introduces a third possible state of
the highlight: ActiveHidden.
Button events are actually handled twofold:
1. KeyPress for printable characters is handled on TextInput,
2. KeyPress for special characters (e.g. enter, arrows)
is handled on KeyDown.
Before this patch, KeyPress was fired too early, so that for
special characters (2) it fired BEFORE KeyDown.
This was not a problem for printable characters, as they
fired on TextInput, which always fired separately after KeyDown.
This patch ensures that KeyPress is fired always in the proper order.
The option "Include Hidden" was not working properly,
as it did not exclude all hidden objects when disabled.
An object is hidden also when its ancestor is not visible.
This ensures that highlight does not become outdated
(e.g. when the highlighted content moves).
Additionally, it makes the highlight have 3px independently of zoom.
This patch improves performance of ecma_conversions::round_to_even():
1. by using f64::round_ties_even(), which has been stable
since 1.77.0, instead of a custom algorithm; and
2. by removing an unnecessary comparison to i32::MIN,
as casting a float to an integer automatically saturates
values smaller than the minimum integer value to the minimum
value of the integer type.
The logic of dropping the focus when it's removed
applies not only to TextFields but for every InteractiveObject.
This patch ensures that any focus is dropped when its parent is removed.
All the interactive objects had the has_focus flag in their concrete
implementations (even AVM2 button, which did not use it at all).
This patch moves it to InteractiveObject (as a bit flag), making it
easier to manage and use through the has_focus, set_has_focus methods.
Additionally, the operation of setting the current focus to None
when an object was having it was popular enough that it warranted its
own method of drop_focus.
This patch adds support for saving files on web using FileReference.
When writing data, a download is triggered with the default file name.
Currently, there's no dialog that lets the user select save destination.
This patch also ensures that all implementations of FileDialogResult
behave the same way: desktop, web, and tests.
The methods `write` and `refresh` have been merged into one:
`write_and_refresh`, which allows the tests and web implementations
behave the same way as desktop.
* avm2: XML.contains() implemented
* tests: XMLList known_failure removed
* avm2: XML manual checks removed
* avm2: XMLList.contains() behavior fixed
* avm2: XML contains method moved to xml.rs
* avm2: Empty line removed in xml.rs
* avm2: XML contains args name renamed to value
Before this patch, focus handlers were called from on_focus_changed,
only for selected objects. It seems that they should be called for
every object by default.
The field tab_enabled in AVM1 is effectively read-only as
tabEnabled is not a built-in property of objects.
That is not the case in AVM2, where tab_enabled is native.
This patch covers both of these cases by introducing methods:
* tab_enabled,
* set_tab_enabled,
* tab_enabled_avm1,
* tab_enabled_avm2_default.
Stage overrides some properties from its super classes.
Some of them are currently identical in Ruffle,
but in FP they are used for e.g. security checks.
Since objects may be hovered using Tab, mouse hover update cannot happen
every time, because it would always reset the hover set by Tab.
This is why mouse hover is skipped when the following are true:
1. the mouse has not moved,
2. no mouse button has been pressed,
3. there was a hover,
4. the hovered object did not disappear.
Originally, FocusTracker operated on DisplayObjects. However, in both
AVM1 and AVM2, all objects that are focusable are InteractiveObjects.
Switching FocusTracker to operate on InteractiveObjects simplifies code
in the long run.
UpdateContext.mouse_over_object and UpdateContext.mouse_down_object
were treated differently from other properties. Changes made to them
were required to be applied manually after using the context.
However, when reborrowing the context, all changes made to these
properties were dropped, as they were not applied after reborrow.
Applying them after reborrow would be difficult (RAII, concurrency
problems), that's why they were replaced with mutable references.
MouseData struct was introduced to make the code more readable and
adhere to the convention of UpdateContext to contain mutable references
to higher level objects.
Since is_focusable has been moved to InteractiveObject,
it may now return true by default, because non-interactive objects
do not implement this method anymore.
Only interactive objects may be focusable, so keeping these methods
in all display objects makes matters more difficult, as they also
has to be implemented for non-interactive objects.
Moving these to InteractiveObject simplifies code in the long run.
This is added for convenience so that it's not required
to cast an object to InteractiveObject when checking for a highlight.
This also gets rid of InteractiveObject.focus_rect_supported,
as `is_highlightable` may be now overridden.
These objects include:
* invisible objects along with their children,
* non-editable text fields.
The method `is_tab_enabled` is renamed to `is_tabbable`, as it no
longer represents the `tabEnabled` property value, but also
incorporates other logic.
The field `focus_rect` is available for each interactive object,
and its value is used to control highlight rendering.
The notable exception is the TextField, which despite having a focus,
is incapable of rendering a highlight.
That is why the `focus_rect_supported` field has been added
to control this behavior.
The property `_focusrect` allows specifying whether an object should be
highlighted by keyboard focus. For SWF <=5 that was possible globally,
and since SWF 6 the global option only has been the default,
which may be overridden using a local `_focusrect` property.
The default value of `MovieClip.tabEnabled` is neither trivial
nor well-documented. This patch implements the logic behind
the default value of `MovieClip.tabEnabled` and covers it with tests.
This patch implements rendering of the yellow rectangle around
a focused element after pressing Tab. Focus tracker which is responsible
for keeping track of the current focus is now also responsible
for keeping track of the highlight and rendering thereof.
This patch improves the order of automatic tab ordering,
and makes it behave more like in FP.
The order after this patch is far from being exactly
the same as in FP, but is close enough.
The property `MovieClip.tabChildren` allows changing the behavior of
tab ordering hierarchically. When set to `false`, it excludes the whole
subtree represented by the movie clip from tab ordering.
The Tab key is used to cycle through focusable elements in stage.
It supports two tab orderings: automatic and custom.
This patch adds basic support for this behavior.
Turns out the Vector to_string function that I added (#15562) was just duplicating what the
default implementation did. The Namespace one was just plain wrong.
Fixes#15474
Turns out that the comment from `set_root_movie` was right all along!
`set_root_movie` should not be used when replacing the root movie,
because it will leave out unclosed resources.
This patch introduces `replace_root_movie`, which is dedicated to be
used when replacing (instead of just setting) the root movie.
The best solution would be to use RAII, e.g. destroy and recreate
the whole Player instance, but this patch is a first step towards
proper resource control.
Currently when clicking in a textbox, the cursor will only be placed
correctly if you click exactly on top of a letter. The basic purpose of
this commit is to make it so that clicking in the surrounding margin
will put the cursor in the closest available position, as in Flash Player.
The logic is a bit complex because a single row of text can contain
multiple layout boxes, each with a different Y offset (but all with the
same Y extent). To be accurate, we need to treat each layout box in the
row "as if" it had the same Y offset that the tallest box in its row has.
This commit also contains a fix for an issue where lower_from_text_spans
was passing the wrong text strings and indexes to fixup_line for
newlines, which needed to be fixed in order to be able to click to place
the cursor in an empty row that was created by newlines.
This has several advantages:
1. it allows using async variants of send and recv,
2. it adds consistency as until now Receiver was async,
and Sender was not.
We hit a pathological case in House
(https://github.com/ruffle-rs/ruffle/issues/15154),
where eagerly decoding bitmaps during preloading results in
over 10GB of ram being used.
With this PR, we store the compressed bitmap, and only decode it
each time we instantiate it. In order to support bitmap fills,
we store the decoded width/height and a lazily-initialized GPU handle
in `Character::Bitmap`