core: Move has_focus to InteractiveObject

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 commit is contained in:
Kamil Jarosz 2024-05-06 13:03:09 +02:00 committed by Nathan Adams
parent 19b7cc9025
commit 8d50d1fead
6 changed files with 36 additions and 62 deletions

View File

@ -45,7 +45,6 @@ pub struct Avm1ButtonData<'gc> {
tracking: Cell<ButtonTracking>, tracking: Cell<ButtonTracking>,
object: Lock<Option<Object<'gc>>>, object: Lock<Option<Object<'gc>>>,
initialized: Cell<bool>, initialized: Cell<bool>,
has_focus: Cell<bool>,
} }
#[derive(Clone, Collect)] #[derive(Clone, Collect)]
@ -98,7 +97,6 @@ impl<'gc> Avm1Button<'gc> {
} else { } else {
ButtonTracking::Push ButtonTracking::Push
}), }),
has_focus: Cell::new(false),
}, },
)) ))
} }
@ -393,11 +391,7 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> {
} }
fn avm1_unload(&self, context: &mut UpdateContext<'_, 'gc>) { fn avm1_unload(&self, context: &mut UpdateContext<'_, 'gc>) {
let had_focus = self.0.has_focus.get(); self.drop_focus(context);
if had_focus {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
if let Some(node) = self.maskee() { if let Some(node) = self.maskee() {
node.set_masker(context.gc(), None, true); node.set_masker(context.gc(), None, true);
} else if let Some(node) = self.masker() { } else if let Some(node) = self.masker() {
@ -530,7 +524,7 @@ impl<'gc> TInteractiveObject<'gc> for Avm1Button<'gc> {
if let Some(name) = event.method_name() { if let Some(name) = event.method_name() {
// Keyboard events don't fire their methods // Keyboard events don't fire their methods
// unless the Button has focus (like for MovieClips). // unless the Button has focus (like for MovieClips).
if !event.is_key_event() || self.0.has_focus.get() { if !event.is_key_event() || self.has_focus() {
context.action_queue.queue_action( context.action_queue.queue_action(
self_display_object, self_display_object,
ActionType::Method { ActionType::Method {
@ -608,15 +602,6 @@ impl<'gc> TInteractiveObject<'gc> for Avm1Button<'gc> {
} }
} }
fn on_focus_changed(
&self,
_context: &mut UpdateContext<'_, 'gc>,
focused: bool,
_other: Option<InteractiveObject<'gc>>,
) {
self.0.has_focus.set(focused);
}
fn tab_enabled_avm1(&self, context: &mut UpdateContext<'_, 'gc>) -> bool { fn tab_enabled_avm1(&self, context: &mut UpdateContext<'_, 'gc>) -> bool {
self.get_avm1_boolean_property(context, "tabEnabled", |_| true) self.get_avm1_boolean_property(context, "tabEnabled", |_| true)
} }

View File

@ -74,7 +74,6 @@ pub struct Avm2ButtonData<'gc> {
/// The AVM2 representation of this button. /// The AVM2 representation of this button.
object: Lock<Option<Avm2Object<'gc>>>, object: Lock<Option<Avm2Object<'gc>>>,
has_focus: Cell<bool>,
enabled: Cell<bool>, enabled: Cell<bool>,
use_hand_cursor: Cell<bool>, use_hand_cursor: Cell<bool>,
@ -135,7 +134,6 @@ impl<'gc> Avm2Button<'gc> {
} else { } else {
ButtonTracking::Push ButtonTracking::Push
}), }),
has_focus: Cell::new(false),
enabled: Cell::new(true), enabled: Cell::new(true),
use_hand_cursor: Cell::new(true), use_hand_cursor: Cell::new(true),
skip_current_frame: Cell::new(false), skip_current_frame: Cell::new(false),
@ -823,15 +821,6 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> {
} }
} }
fn on_focus_changed(
&self,
_context: &mut UpdateContext<'_, 'gc>,
focused: bool,
_other: Option<InteractiveObject<'gc>>,
) {
self.0.has_focus.set(focused);
}
fn tab_enabled_avm2_default(&self, _context: &mut UpdateContext<'_, 'gc>) -> bool { fn tab_enabled_avm2_default(&self, _context: &mut UpdateContext<'_, 'gc>) -> bool {
true true
} }

View File

@ -987,7 +987,7 @@ impl<'gc> EditText<'gc> {
..Default::default() ..Default::default()
}); });
let visible_selection = if edit_text.flags.contains(EditTextFlag::HAS_FOCUS) { let visible_selection = if self.has_focus() {
edit_text.selection edit_text.selection
} else { } else {
None None
@ -2121,7 +2121,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
let has_parent = self.parent().is_some(); let has_parent = self.parent().is_some();
if self.movie().is_action_script_3() && had_parent && !has_parent { if self.movie().is_action_script_3() && had_parent && !has_parent {
let had_focus = self.0.read().flags.contains(EditTextFlag::HAS_FOCUS); let had_focus = self.has_focus();
if had_focus { if had_focus {
let tracker = context.focus_tracker; let tracker = context.focus_tracker;
tracker.set(None, context); tracker.set(None, context);
@ -2237,7 +2237,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
}); });
if edit_text.layout.is_empty() && !edit_text.flags.contains(EditTextFlag::READ_ONLY) { if edit_text.layout.is_empty() && !edit_text.flags.contains(EditTextFlag::READ_ONLY) {
let visible_selection = if edit_text.flags.contains(EditTextFlag::HAS_FOCUS) { let visible_selection = if self.has_focus() {
edit_text.selection edit_text.selection
} else { } else {
None None
@ -2275,11 +2275,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
} }
fn avm1_unload(&self, context: &mut UpdateContext<'_, 'gc>) { fn avm1_unload(&self, context: &mut UpdateContext<'_, 'gc>) {
let had_focus = self.0.read().flags.contains(EditTextFlag::HAS_FOCUS); self.drop_focus(context);
if had_focus {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
if let Some(node) = self.maskee() { if let Some(node) = self.maskee() {
node.set_masker(context.gc_context, None, true); node.set_masker(context.gc_context, None, true);
@ -2459,11 +2455,9 @@ impl<'gc> TInteractiveObject<'gc> for EditText<'gc> {
focused: bool, focused: bool,
_other: Option<InteractiveObject<'gc>>, _other: Option<InteractiveObject<'gc>>,
) { ) {
let is_action_script_3 = self.movie().is_action_script_3(); let is_avm1 = !self.movie().is_action_script_3();
let mut text = self.0.write(context.gc_context); if !focused && is_avm1 {
text.flags.set(EditTextFlag::HAS_FOCUS, focused); self.0.write(context.gc_context).selection = None;
if !focused && !is_action_script_3 {
text.selection = None;
} }
} }
@ -2494,7 +2488,6 @@ bitflags::bitflags! {
struct EditTextFlag: u16 { struct EditTextFlag: u16 {
const FIRING_VARIABLE_BINDING = 1 << 0; const FIRING_VARIABLE_BINDING = 1 << 0;
const HAS_BACKGROUND = 1 << 1; const HAS_BACKGROUND = 1 << 1;
const HAS_FOCUS = 1 << 2;
// The following bits need to match `swf::EditTextFlag`. // The following bits need to match `swf::EditTextFlag`.
const READ_ONLY = 1 << 3; const READ_ONLY = 1 << 3;

View File

@ -76,6 +76,9 @@ bitflags! {
/// Whether this `InteractiveObject` accepts double-clicks. /// Whether this `InteractiveObject` accepts double-clicks.
const DOUBLE_CLICK_ENABLED = 1 << 1; const DOUBLE_CLICK_ENABLED = 1 << 1;
/// Whether this `InteractiveObject` is currently focused.
const HAS_FOCUS = 1 << 2;
} }
} }
@ -167,6 +170,18 @@ pub trait TInteractiveObject<'gc>:
.set(InteractiveObjectFlags::DOUBLE_CLICK_ENABLED, value) .set(InteractiveObjectFlags::DOUBLE_CLICK_ENABLED, value)
} }
fn has_focus(self) -> bool {
self.raw_interactive()
.flags
.contains(InteractiveObjectFlags::HAS_FOCUS)
}
fn set_has_focus(self, mc: &Mutation<'gc>, value: bool) {
self.raw_interactive_mut(mc)
.flags
.set(InteractiveObjectFlags::HAS_FOCUS, value)
}
fn context_menu(self) -> Avm2Value<'gc> { fn context_menu(self) -> Avm2Value<'gc> {
self.raw_interactive().context_menu self.raw_interactive().context_menu
} }
@ -536,6 +551,14 @@ pub trait TInteractiveObject<'gc>:
) { ) {
} }
/// If this object has focus, this method drops it.
fn drop_focus(&self, context: &mut UpdateContext<'_, 'gc>) {
if self.has_focus() {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
}
fn call_focus_handler( fn call_focus_handler(
&self, &self,
context: &mut UpdateContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,

View File

@ -162,7 +162,6 @@ pub struct MovieClipData<'gc> {
avm2_class: Option<Avm2ClassObject<'gc>>, avm2_class: Option<Avm2ClassObject<'gc>>,
#[collect(require_static)] #[collect(require_static)]
drawing: Drawing, drawing: Drawing,
has_focus: bool,
avm2_enabled: bool, avm2_enabled: bool,
/// Show a hand cursor when the clip is in button mode. /// Show a hand cursor when the clip is in button mode.
@ -212,7 +211,6 @@ impl<'gc> MovieClip<'gc> {
flags: MovieClipFlags::empty(), flags: MovieClipFlags::empty(),
avm2_class: None, avm2_class: None,
drawing: Drawing::new(), drawing: Drawing::new(),
has_focus: false,
avm2_enabled: true, avm2_enabled: true,
avm2_use_hand_cursor: true, avm2_use_hand_cursor: true,
button_mode: false, button_mode: false,
@ -255,7 +253,6 @@ impl<'gc> MovieClip<'gc> {
flags: MovieClipFlags::empty(), flags: MovieClipFlags::empty(),
avm2_class: Some(class), avm2_class: Some(class),
drawing: Drawing::new(), drawing: Drawing::new(),
has_focus: false,
avm2_enabled: true, avm2_enabled: true,
avm2_use_hand_cursor: true, avm2_use_hand_cursor: true,
button_mode: false, button_mode: false,
@ -299,7 +296,6 @@ impl<'gc> MovieClip<'gc> {
flags: MovieClipFlags::PLAYING, flags: MovieClipFlags::PLAYING,
avm2_class: None, avm2_class: None,
drawing: Drawing::new(), drawing: Drawing::new(),
has_focus: false,
avm2_enabled: true, avm2_enabled: true,
avm2_use_hand_cursor: true, avm2_use_hand_cursor: true,
button_mode: false, button_mode: false,
@ -368,7 +364,6 @@ impl<'gc> MovieClip<'gc> {
flags: MovieClipFlags::PLAYING, flags: MovieClipFlags::PLAYING,
avm2_class: None, avm2_class: None,
drawing: Drawing::new(), drawing: Drawing::new(),
has_focus: false,
avm2_enabled: true, avm2_enabled: true,
avm2_use_hand_cursor: true, avm2_use_hand_cursor: true,
button_mode: false, button_mode: false,
@ -2962,11 +2957,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
} }
} }
let had_focus = self.0.read().has_focus; self.drop_focus(context);
if had_focus {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
{ {
let mut mc = self.0.write(context.gc_context); let mut mc = self.0.write(context.gc_context);
@ -3101,7 +3092,7 @@ impl<'gc> TInteractiveObject<'gc> for MovieClip<'gc> {
if swf_version >= 6 { if swf_version >= 6 {
if let Some(name) = event.method_name() { if let Some(name) = event.method_name() {
// Keyboard events don't fire their methods unless the MovieClip has focus (#2120). // Keyboard events don't fire their methods unless the MovieClip has focus (#2120).
if !event.is_key_event() || read.has_focus { if !event.is_key_event() || self.has_focus() {
context.action_queue.queue_action( context.action_queue.queue_action(
self.into(), self.into(),
ActionType::Method { ActionType::Method {
@ -3386,15 +3377,6 @@ impl<'gc> TInteractiveObject<'gc> for MovieClip<'gc> {
} }
} }
fn on_focus_changed(
&self,
context: &mut UpdateContext<'_, 'gc>,
focused: bool,
_other: Option<InteractiveObject<'gc>>,
) {
self.0.write(context.gc_context).has_focus = focused;
}
fn tab_enabled_avm1(&self, context: &mut UpdateContext<'_, 'gc>) -> bool { fn tab_enabled_avm1(&self, context: &mut UpdateContext<'_, 'gc>) -> bool {
self.get_avm1_boolean_property(context, "tabEnabled", |context| { self.get_avm1_boolean_property(context, "tabEnabled", |context| {
self.tab_index().is_some() || self.is_button_mode(context) self.tab_index().is_some() || self.is_button_mode(context)

View File

@ -98,10 +98,12 @@ impl<'gc> FocusTracker<'gc> {
self.update_highlight(context); self.update_highlight(context);
if let Some(old) = old { if let Some(old) = old {
old.set_has_focus(context.gc(), false);
old.on_focus_changed(context, false, new); old.on_focus_changed(context, false, new);
old.call_focus_handler(context, false, new); old.call_focus_handler(context, false, new);
} }
if let Some(new) = new { if let Some(new) = new {
new.set_has_focus(context.gc(), true);
new.on_focus_changed(context, true, old); new.on_focus_changed(context, true, old);
new.call_focus_handler(context, true, old); new.call_focus_handler(context, true, old);
} }