From 76b8b5e4384fac5b7e30a934f1f09e7e4108291f Mon Sep 17 00:00:00 2001 From: Kamil Jarosz Date: Tue, 2 Apr 2024 13:36:02 +0200 Subject: [PATCH] avm1: Add support for _focusrect property 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. --- core/src/avm1/object/stage_object.rs | 53 ++++++++++++++++++++++---- core/src/display_object/interactive.rs | 16 ++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/core/src/avm1/object/stage_object.rs b/core/src/avm1/object/stage_object.rs index ab508a1bd..685b25d7f 100644 --- a/core/src/avm1/object/stage_object.rs +++ b/core/src/avm1/object/stage_object.rs @@ -8,7 +8,7 @@ use crate::avm1::{Object, ObjectPtr, ScriptObject, TObject, Value}; use crate::avm_warn; use crate::context::UpdateContext; use crate::display_object::{ - DisplayObject, EditText, MovieClip, TDisplayObject, TDisplayObjectContainer, + DisplayObject, EditText, MovieClip, TDisplayObject, TDisplayObjectContainer, TInteractiveObject, }; use crate::string::{AvmString, WStr}; use crate::types::Percent; @@ -738,17 +738,56 @@ fn set_high_quality<'gc>( Ok(()) } -fn focus_rect<'gc>(activation: &mut Activation<'_, 'gc>, _this: DisplayObject<'gc>) -> Value<'gc> { - avm_warn!(activation, "Unimplemented property _focusrect"); - Value::Null +fn refers_to_stage_focus_rect<'gc>( + activation: &mut Activation<'_, 'gc>, + this: DisplayObject<'gc>, +) -> bool { + activation.swf_version() <= 5 || this.parent().is_some_and(|p| p.as_stage().is_some()) +} + +fn focus_rect<'gc>(activation: &mut Activation<'_, 'gc>, this: DisplayObject<'gc>) -> Value<'gc> { + if refers_to_stage_focus_rect(activation, this) { + let val = activation.context.stage.stage_focus_rect(); + if activation.swf_version() <= 5 { + Value::Number(if val { 1.0 } else { 0.0 }) + } else { + Value::Bool(val) + } + } else if let Some(obj) = this.as_interactive() { + match obj.focus_rect() { + Some(val) => Value::Bool(val), + None => Value::Null, + } + } else { + Value::Undefined + } } fn set_focus_rect<'gc>( activation: &mut Activation<'_, 'gc>, - _this: DisplayObject<'gc>, - _val: Value<'gc>, + this: DisplayObject<'gc>, + val: Value<'gc>, ) -> Result<(), Error<'gc>> { - avm_warn!(activation, "Unimplemented property _focusrect"); + if refers_to_stage_focus_rect(activation, this) { + let val = match val { + Value::Undefined | Value::Null => { + // undefined & null are ignored + return Ok(()); + } + Value::Object(_) => false, + _ => val.coerce_to_f64(activation)? != 0.0, + }; + activation + .context + .stage + .set_stage_focus_rect(activation.context.gc(), val); + } else if let Some(obj) = this.as_interactive() { + let val = match val { + Value::Undefined | Value::Null => None, + _ => Some(val.as_bool(activation.swf_version())), + }; + obj.set_focus_rect(activation.context.gc(), val); + } Ok(()) } diff --git a/core/src/display_object/interactive.rs b/core/src/display_object/interactive.rs index 1cabda5b4..e06c34f55 100644 --- a/core/src/display_object/interactive.rs +++ b/core/src/display_object/interactive.rs @@ -89,6 +89,9 @@ pub struct InteractiveObjectBase<'gc> { /// display object. #[collect(require_static)] last_click: Option, + + /// Specifies whether this object displays a yellow rectangle when focused. + focus_rect: Option, } impl<'gc> Default for InteractiveObjectBase<'gc> { @@ -98,6 +101,7 @@ impl<'gc> Default for InteractiveObjectBase<'gc> { flags: InteractiveObjectFlags::MOUSE_ENABLED, context_menu: Avm2Value::Null, last_click: None, + focus_rect: None, } } } @@ -159,6 +163,18 @@ pub trait TInteractiveObject<'gc>: self.raw_interactive_mut(mc).context_menu = value; } + /// Get the boolean flag which determines whether objects display a glowing border + /// when they have focus. + fn focus_rect(self) -> Option { + self.raw_interactive().focus_rect + } + + /// Set the boolean flag which determines whether objects display a glowing border + /// when they have focus. + fn set_focus_rect(self, mc: &Mutation<'gc>, value: Option) { + self.raw_interactive_mut(mc).focus_rect = value; + } + /// Filter the incoming clip event. /// /// If this returns `Handled`, then the rest of the event handling