avm1: Implement Selection index getters & setSelection - #271

This commit is contained in:
Nathan Adams 2020-10-30 23:13:07 +01:00 committed by Mike Welsh
parent 3f2057b53e
commit a4a2cd00b2
3 changed files with 164 additions and 10 deletions

View File

@ -3,33 +3,94 @@ use crate::avm1::error::Error;
use crate::avm1::globals::as_broadcaster::BroadcasterFunctions; use crate::avm1::globals::as_broadcaster::BroadcasterFunctions;
use crate::avm1::property::Attribute; use crate::avm1::property::Attribute;
use crate::avm1::{Object, ScriptObject, TDisplayObject, TObject, Value}; use crate::avm1::{Object, ScriptObject, TDisplayObject, TObject, Value};
use crate::display_object::{EditText, TextSelection};
use gc_arena::MutationContext; use gc_arena::MutationContext;
pub fn get_begin_index<'gc>( pub fn get_begin_index<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_this: Object<'gc>, _this: Object<'gc>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
// TODO: Implement if let Some(selection) = activation
Ok(Value::Number(-1.0)) .context
.focus_tracker
.get()
.and_then(|o| o.as_edit_text())
.and_then(EditText::get_selection)
{
Ok(Value::Number(selection.start() as f64))
} else {
Ok(Value::Number(-1.0))
}
} }
pub fn get_end_index<'gc>( pub fn get_end_index<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_this: Object<'gc>, _this: Object<'gc>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
// TODO: Implement if let Some(selection) = activation
Ok(Value::Number(-1.0)) .context
.focus_tracker
.get()
.and_then(|o| o.as_edit_text())
.and_then(EditText::get_selection)
{
Ok(Value::Number(selection.end() as f64))
} else {
Ok(Value::Number(-1.0))
}
} }
pub fn get_caret_index<'gc>( pub fn get_caret_index<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_this: Object<'gc>, _this: Object<'gc>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
// TODO: Implement if let Some(selection) = activation
Ok(Value::Number(-1.0)) .context
.focus_tracker
.get()
.and_then(|o| o.as_edit_text())
.and_then(EditText::get_selection)
{
Ok(Value::Number(selection.to() as f64))
} else {
Ok(Value::Number(-1.0))
}
}
pub fn set_selection<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if args.is_empty() {
return Ok(Value::Undefined);
}
if let Some(edit_box) = activation
.context
.focus_tracker
.get()
.and_then(|o| o.as_edit_text())
{
let start = args
.get(0)
.map(|v| v.coerce_to_i32(activation))
.transpose()?
.unwrap_or(0);
let end = args
.get(1)
.map(|v| v.coerce_to_i32(activation))
.transpose()?
.unwrap_or(i32::max_value());
let start = if start < 0 { 0 } else { start as usize };
let end = if end < 0 { 0 } else { end as usize };
let selection = TextSelection::for_range(start, end);
edit_box.set_selection(Some(selection), activation.context.gc_context);
}
Ok(Value::Undefined)
} }
pub fn get_focus<'gc>( pub fn get_focus<'gc>(
@ -106,6 +167,14 @@ pub fn create_selection_object<'gc>(
Some(fn_proto), Some(fn_proto),
); );
object.force_set_function(
"setSelection",
set_selection,
gc_context,
Attribute::DontDelete | Attribute::DontEnum | Attribute::ReadOnly,
Some(fn_proto),
);
object.force_set_function( object.force_set_function(
"setFocus", "setFocus",
set_focus, set_focus,

View File

@ -28,7 +28,7 @@ use crate::backend::input::MouseCursor;
use crate::events::{ClipEvent, ClipEventResult}; use crate::events::{ClipEvent, ClipEventResult};
pub use bitmap::Bitmap; pub use bitmap::Bitmap;
pub use button::Button; pub use button::Button;
pub use edit_text::{AutoSizeMode, EditText}; pub use edit_text::{AutoSizeMode, EditText, TextSelection};
pub use graphic::Graphic; pub use graphic::Graphic;
pub use morph_shape::{MorphShape, MorphShapeStatic}; pub use morph_shape::{MorphShape, MorphShapeStatic};
pub use movie_clip::{MovieClip, Scene}; pub use movie_clip::{MovieClip, Scene};

View File

@ -118,6 +118,9 @@ pub struct EditTextData<'gc> {
/// Whether this text field is firing is variable binding (to prevent infinite loops). /// Whether this text field is firing is variable binding (to prevent infinite loops).
firing_variable_binding: bool, firing_variable_binding: bool,
/// The selected portion of the text, or None if the text is not selected.
selection: Option<TextSelection>,
} }
impl<'gc> EditText<'gc> { impl<'gc> EditText<'gc> {
@ -201,6 +204,7 @@ impl<'gc> EditText<'gc> {
variable, variable,
bound_stage_object: None, bound_stage_object: None,
firing_variable_binding: false, firing_variable_binding: false,
selection: None,
}, },
)); ));
@ -845,6 +849,24 @@ impl<'gc> EditText<'gc> {
.firing_variable_binding = false; .firing_variable_binding = false;
} }
} }
pub fn get_selection(self) -> Option<TextSelection> {
self.0.read().selection.clone()
}
pub fn set_selection(
self,
selection: Option<TextSelection>,
gc_context: MutationContext<'gc, '_>,
) {
let mut text = self.0.write(gc_context);
if let Some(mut selection) = selection {
selection.clamp(text.text_spans.text().len());
text.selection = Some(selection);
} else {
text.selection = None;
}
}
} }
impl<'gc> TDisplayObject<'gc> for EditText<'gc> { impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
@ -1126,3 +1148,66 @@ unsafe impl<'gc> gc_arena::Collect for EditTextStatic {
false false
} }
} }
#[derive(Clone, Debug)]
pub struct TextSelection {
from: usize,
to: usize,
}
unsafe impl Collect for TextSelection {
#[inline]
fn needs_trace() -> bool {
false
}
}
impl TextSelection {
pub fn for_position(position: usize) -> Self {
Self {
from: position,
to: position,
}
}
pub fn for_range(from: usize, to: usize) -> Self {
Self { from, to }
}
/// The "from" part of the range is where the user started the selection.
/// It may be greater than "to", for example if the user dragged a selection box from right to
/// left.
pub fn from(&self) -> usize {
self.from
}
/// The "to" part of the range is where the user ended the selection.
/// This also may be called the caret position - it is the last place the user placed the
/// caret and any text or changes to the range will be done by this position.
/// It may be less than "from", for example if the user dragged a selection box from right to
/// left.
pub fn to(&self) -> usize {
self.to
}
/// The "start" part of the range is the smallest (closest to 0) part of this selection range.
pub fn start(&self) -> usize {
self.from.min(self.to)
}
/// The "end" part of the range is the smallest (closest to 0) part of this selection range.
pub fn end(&self) -> usize {
self.from.max(self.to)
}
/// Clamps this selection to the maximum length provided.
/// Neither from nor to will be greater than this length.
pub fn clamp(&mut self, length: usize) {
if self.from > length {
self.from = length;
}
if self.to > length {
self.to = length;
}
}
}