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::property::Attribute;
|
||||
use crate::avm1::{Object, ScriptObject, TDisplayObject, TObject, Value};
|
||||
use crate::display_object::{EditText, TextSelection};
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
pub fn get_begin_index<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// TODO: Implement
|
||||
Ok(Value::Number(-1.0))
|
||||
if let Some(selection) = activation
|
||||
.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>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// TODO: Implement
|
||||
Ok(Value::Number(-1.0))
|
||||
if let Some(selection) = activation
|
||||
.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>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// TODO: Implement
|
||||
Ok(Value::Number(-1.0))
|
||||
if let Some(selection) = activation
|
||||
.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>(
|
||||
|
@ -106,6 +167,14 @@ pub fn create_selection_object<'gc>(
|
|||
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(
|
||||
"setFocus",
|
||||
set_focus,
|
||||
|
|
|
@ -28,7 +28,7 @@ use crate::backend::input::MouseCursor;
|
|||
use crate::events::{ClipEvent, ClipEventResult};
|
||||
pub use bitmap::Bitmap;
|
||||
pub use button::Button;
|
||||
pub use edit_text::{AutoSizeMode, EditText};
|
||||
pub use edit_text::{AutoSizeMode, EditText, TextSelection};
|
||||
pub use graphic::Graphic;
|
||||
pub use morph_shape::{MorphShape, MorphShapeStatic};
|
||||
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).
|
||||
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> {
|
||||
|
@ -201,6 +204,7 @@ impl<'gc> EditText<'gc> {
|
|||
variable,
|
||||
bound_stage_object: None,
|
||||
firing_variable_binding: false,
|
||||
selection: None,
|
||||
},
|
||||
));
|
||||
|
||||
|
@ -845,6 +849,24 @@ impl<'gc> EditText<'gc> {
|
|||
.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> {
|
||||
|
@ -1126,3 +1148,66 @@ unsafe impl<'gc> gc_arena::Collect for EditTextStatic {
|
|||
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