avm1: Implement Selection index getters & setSelection - #271
This commit is contained in:
parent
3f2057b53e
commit
a4a2cd00b2
|
@ -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,
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue