diff --git a/Cargo.lock b/Cargo.lock
index 7e211cad5eb4..6960601f9bc1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4184,6 +4184,7 @@ dependencies = [
"downcast-rs",
"egui",
"egui_extras",
+ "either",
"encoding_rs",
"enum-map",
"enumset",
diff --git a/core/Cargo.toml b/core/Cargo.toml
index d2f5668bcf4d..cc4cb764af3b 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -66,6 +66,7 @@ ttf-parser = "0.20"
num-bigint = "0.4"
unic-segment = "0.9.0"
id3 = "1.13.1"
+either = "1.10.0"
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
version = "0.3.30"
diff --git a/core/src/avm2/qname.rs b/core/src/avm2/qname.rs
index f70aab3ca34a..59efb7a8a13f 100644
--- a/core/src/avm2/qname.rs
+++ b/core/src/avm2/qname.rs
@@ -1,8 +1,8 @@
use crate::avm2::script::TranslationUnit;
use crate::avm2::{Activation, Error, Namespace};
use crate::context::UpdateContext;
-use crate::either::Either;
use crate::string::{AvmString, WStr, WString};
+use either::Either;
use gc_arena::{Collect, Mutation};
use std::fmt::Debug;
use swf::avm2::types::{Index, Multiname as AbcMultiname};
diff --git a/core/src/either.rs b/core/src/either.rs
deleted file mode 100644
index 4f7167c9e310..000000000000
--- a/core/src/either.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-use std::ops::Deref;
-
-pub enum Either {
- Left(A),
- Right(B),
-}
-
-impl Deref for Either
-where
- A: Deref,
- B: Deref,
-{
- type Target = A::Target;
-
- fn deref(&self) -> &Self::Target {
- match self {
- Self::Left(a) => a,
- Self::Right(b) => b,
- }
- }
-}
diff --git a/core/src/focus_tracker.rs b/core/src/focus_tracker.rs
index 020108368fd0..50b5f0f793e3 100644
--- a/core/src/focus_tracker.rs
+++ b/core/src/focus_tracker.rs
@@ -4,6 +4,7 @@ use crate::context::UpdateContext;
pub use crate::display_object::{
DisplayObject, TDisplayObject, TDisplayObjectContainer, TextSelection,
};
+use either::Either;
use gc_arena::lock::GcLock;
use gc_arena::{Collect, Mutation};
@@ -67,7 +68,7 @@ impl<'gc> FocusTracker<'gc> {
}
}
- pub fn cycle(&self, context: &mut UpdateContext<'_, 'gc>) {
+ pub fn cycle(&self, context: &mut UpdateContext<'_, 'gc>, reverse: bool) {
let stage = context.stage;
let mut tab_order = vec![];
stage.fill_tab_order(&mut tab_order, context);
@@ -88,16 +89,23 @@ impl<'gc> FocusTracker<'gc> {
tab_order.sort_by_key(|o| o.tab_index());
}
+ let mut tab_order = if reverse {
+ Either::Left(tab_order.iter().rev())
+ } else {
+ Either::Right(tab_order.iter())
+ }
+ .peekable();
+ let first = tab_order.peek().copied();
+
let next = if let Some(current_focus) = self.0.get() {
// Find the next object which should take the focus.
tab_order
- .iter()
.skip_while(|o| o.as_ptr() != current_focus.as_ptr())
.nth(1)
- .or(tab_order.first())
+ .or(first)
} else {
// If no focus is present, we start from the beginning.
- tab_order.first()
+ first
};
self.set(next.copied(), context);
diff --git a/core/src/lib.rs b/core/src/lib.rs
index fc43e80845b6..2a76613d5e66 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -29,7 +29,6 @@ pub mod context;
pub mod context_menu;
mod drawing;
mod ecma_conversions;
-pub(crate) mod either;
pub mod events;
pub mod focus_tracker;
mod font;
diff --git a/core/src/player.rs b/core/src/player.rs
index ea1772b4f962..bd47e1005a0b 100644
--- a/core/src/player.rs
+++ b/core/src/player.rs
@@ -1186,8 +1186,9 @@ impl Player {
} = event
{
self.mutate_with_update_context(|context| {
+ let reversed = context.input.is_key_down(KeyCode::Shift);
let tracker = context.focus_tracker;
- tracker.cycle(context);
+ tracker.cycle(context, reversed);
});
}
}
diff --git a/tests/framework/src/runner.rs b/tests/framework/src/runner.rs
index 953b1bf35745..30756907c246 100644
--- a/tests/framework/src/runner.rs
+++ b/tests/framework/src/runner.rs
@@ -230,6 +230,10 @@ impl TestRunner {
key_code: KeyCode::from_u8(*key_code).expect("Invalid keycode in test"),
key_char: None,
},
+ AutomatedEvent::KeyUp { key_code } => PlayerEvent::KeyUp {
+ key_code: KeyCode::from_u8(*key_code).expect("Invalid keycode in test"),
+ key_char: None,
+ },
AutomatedEvent::TextInput { codepoint } => PlayerEvent::TextInput {
codepoint: *codepoint,
},
diff --git a/tests/input-format/src/format.rs b/tests/input-format/src/format.rs
index a60ac59bed44..71540c338832 100644
--- a/tests/input-format/src/format.rs
+++ b/tests/input-format/src/format.rs
@@ -65,6 +65,9 @@ pub enum AutomatedEvent {
/// Press a key
KeyDown { key_code: u8 },
+ /// Release a key
+ KeyUp { key_code: u8 },
+
/// Input a character code
TextInput { codepoint: char },
diff --git a/tests/input-format/src/injector.rs b/tests/input-format/src/injector.rs
index a0d326c15b33..28a120f5dd95 100644
--- a/tests/input-format/src/injector.rs
+++ b/tests/input-format/src/injector.rs
@@ -96,6 +96,7 @@ impl InputInjector {
AutomatedEvent::Wait => break,
AutomatedEvent::MouseMove { .. }
| AutomatedEvent::KeyDown { .. }
+ | AutomatedEvent::KeyUp { .. }
| AutomatedEvent::TextInput { .. }
| AutomatedEvent::TextControl { .. }
| AutomatedEvent::SetClipboardText { .. } => {}
diff --git a/tests/tests/swfs/avm1/tab_ordering_reverse/input.json b/tests/tests/swfs/avm1/tab_ordering_reverse/input.json
new file mode 100644
index 000000000000..3ca80b630b38
--- /dev/null
+++ b/tests/tests/swfs/avm1/tab_ordering_reverse/input.json
@@ -0,0 +1,24 @@
+[
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyDown", "key_code": 16 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyUp", "key_code": 16 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyDown", "key_code": 16 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyUp", "key_code": 16 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyDown", "key_code": 17 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyUp", "key_code": 17 },
+ { "type": "KeyDown", "key_code": 16 },
+ { "type": "KeyDown", "key_code": 17 },
+ { "type": "KeyDown", "key_code": 9 },
+ { "type": "KeyUp", "key_code": 17 },
+ { "type": "KeyUp", "key_code": 16 },
+ { "type": "KeyDown", "key_code": 9 }
+]
diff --git a/tests/tests/swfs/avm1/tab_ordering_reverse/output.txt b/tests/tests/swfs/avm1/tab_ordering_reverse/output.txt
new file mode 100644
index 000000000000..1ee971e0ff60
--- /dev/null
+++ b/tests/tests/swfs/avm1/tab_ordering_reverse/output.txt
@@ -0,0 +1,51 @@
+Focus changed
+ old: null
+ new: _level0.text1
+Tab pressed
+Focus changed
+ old: _level0.text1
+ new: _level0.text2
+Tab pressed
+Focus changed
+ old: _level0.text2
+ new: _level0.text3
+Tab pressed
+Focus changed
+ old: _level0.text3
+ new: _level0.text2
+Tab pressed
+Focus changed
+ old: _level0.text2
+ new: _level0.text3
+Tab pressed
+Focus changed
+ old: _level0.text3
+ new: _level0.text2
+Tab pressed
+Focus changed
+ old: _level0.text2
+ new: _level0.text1
+Tab pressed
+Focus changed
+ old: _level0.text1
+ new: _level0.text4
+Tab pressed
+Focus changed
+ old: _level0.text4
+ new: _level0.text1
+Tab pressed
+Focus changed
+ old: _level0.text1
+ new: _level0.text2
+Tab pressed
+Focus changed
+ old: _level0.text2
+ new: _level0.text3
+Tab pressed
+Focus changed
+ old: _level0.text3
+ new: _level0.text2
+Tab pressed
+Focus changed
+ old: _level0.text2
+ new: _level0.text3
diff --git a/tests/tests/swfs/avm1/tab_ordering_reverse/test.as b/tests/tests/swfs/avm1/tab_ordering_reverse/test.as
new file mode 100644
index 000000000000..9633923eb424
--- /dev/null
+++ b/tests/tests/swfs/avm1/tab_ordering_reverse/test.as
@@ -0,0 +1,11 @@
+var listener = new Object();
+listener.onSetFocus = function(oldFocus, newFocus) {
+ if (newFocus) {
+ trace("Focus changed");
+ trace(" old: " + oldFocus);
+ trace(" new: " + newFocus);
+ }
+};
+Selection.addListener(listener);
+
+Selection.setFocus(text1);
diff --git a/tests/tests/swfs/avm1/tab_ordering_reverse/test.swf b/tests/tests/swfs/avm1/tab_ordering_reverse/test.swf
new file mode 100644
index 000000000000..358e00590e04
Binary files /dev/null and b/tests/tests/swfs/avm1/tab_ordering_reverse/test.swf differ
diff --git a/tests/tests/swfs/avm1/tab_ordering_reverse/test.toml b/tests/tests/swfs/avm1/tab_ordering_reverse/test.toml
new file mode 100644
index 000000000000..cf6123969a1d
--- /dev/null
+++ b/tests/tests/swfs/avm1/tab_ordering_reverse/test.toml
@@ -0,0 +1 @@
+num_ticks = 1