diff --git a/core/src/display_object/avm1_button.rs b/core/src/display_object/avm1_button.rs index 712dfef4a..65431e434 100644 --- a/core/src/display_object/avm1_button.rs +++ b/core/src/display_object/avm1_button.rs @@ -52,6 +52,8 @@ pub struct Avm1ButtonData<'gc> { struct Avm1ButtonDataMut<'gc> { base: InteractiveObjectBase<'gc>, hit_area: BTreeMap>, + #[collect(require_static)] + hit_bounds: Rectangle, container: ChildContainer<'gc>, } @@ -73,6 +75,7 @@ impl<'gc> Avm1Button<'gc> { base: Default::default(), container: ChildContainer::new(source_movie.movie.clone()), hit_area: BTreeMap::new(), + hit_bounds: Default::default(), }), static_data: Gc::new( mc, @@ -126,11 +129,7 @@ impl<'gc> Avm1Button<'gc> { /// /// This function instantiates children and thus must not be called whilst /// the caller is holding a write lock on the button data. - pub fn set_state( - mut self, - context: &mut crate::context::UpdateContext<'_, 'gc>, - state: ButtonState, - ) { + pub fn set_state(mut self, context: &mut UpdateContext<'_, 'gc>, state: ButtonState) { let mut removed_depths: fnv::FnvHashSet<_> = self.iter_render_list().map(|o| o.depth()).collect(); @@ -335,10 +334,13 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> { } let write = unlock!(Gc::write(context.gc(), self.0), Avm1ButtonData, cell); + let mut hit_bounds = Rectangle::INVALID; for (child, depth) in new_children { child.post_instantiation(context, None, Instantiator::Movie, false); write.borrow_mut().hit_area.insert(depth, child); + hit_bounds = hit_bounds.union(&child.local_bounds()); } + write.borrow_mut().hit_bounds = hit_bounds; } } @@ -600,6 +602,15 @@ impl<'gc> TInteractiveObject<'gc> for Avm1Button<'gc> { fn tab_enabled_avm1(&self, context: &mut UpdateContext<'_, 'gc>) -> bool { self.get_avm1_boolean_property(context, "tabEnabled", |_| true) } + + fn highlight_bounds(self) -> Rectangle { + // 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). + self.local_to_global_matrix() * self.0.cell.borrow().hit_bounds.clone() + } } impl<'gc> Avm1ButtonData<'gc> { diff --git a/swf/src/types/rectangle.rs b/swf/src/types/rectangle.rs index 2479290ff..f0b353939 100644 --- a/swf/src/types/rectangle.rs +++ b/swf/src/types/rectangle.rs @@ -58,7 +58,7 @@ impl Rectangle { } impl Rectangle { - const INVALID: Self = Self { + pub const INVALID: Self = Self { x_min: T::INVALID, x_max: T::INVALID, y_min: T::INVALID,