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.
This commit is contained in:
Kamil Jarosz 2024-04-02 13:36:02 +02:00 committed by Nathan Adams
parent da9ee141dc
commit 76b8b5e438
2 changed files with 62 additions and 7 deletions

View File

@ -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(())
}

View File

@ -89,6 +89,9 @@ pub struct InteractiveObjectBase<'gc> {
/// display object.
#[collect(require_static)]
last_click: Option<Instant>,
/// Specifies whether this object displays a yellow rectangle when focused.
focus_rect: Option<bool>,
}
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<bool> {
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<bool>) {
self.raw_interactive_mut(mc).focus_rect = value;
}
/// Filter the incoming clip event.
///
/// If this returns `Handled`, then the rest of the event handling