diff --git a/core/src/avm2/globals/flash/text/textfield.rs b/core/src/avm2/globals/flash/text/textfield.rs index a854107fc..4e53c0661 100644 --- a/core/src/avm2/globals/flash/text/textfield.rs +++ b/core/src/avm2/globals/flash/text/textfield.rs @@ -1241,6 +1241,41 @@ pub fn set_scroll_h<'gc>( Ok(Value::Undefined) } +pub fn max_chars<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + if let Some(this) = this + .and_then(|this| this.as_display_object()) + .and_then(|this| this.as_edit_text()) + { + return Ok(this.max_chars().into()); + } + + Ok(Value::Undefined) +} + +pub fn set_max_chars<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + if let Some(this) = this + .and_then(|this| this.as_display_object()) + .and_then(|this| this.as_edit_text()) + { + let input = args + .get(0) + .cloned() + .unwrap_or(Value::Undefined) + .coerce_to_i32(activation)?; + this.set_max_chars(input, &mut activation.context); + } + + Ok(Value::Undefined) +} + /// Construct `TextField`'s class. pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { let class = Class::new( @@ -1288,6 +1323,7 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc> ("length", Some(length), None), ("maxScrollH", Some(max_scroll_h), None), ("maxScrollV", Some(max_scroll_v), None), + ("maxChars", Some(max_chars), Some(set_max_chars)), ("multiline", Some(multiline), Some(set_multiline)), ("scrollH", Some(scroll_h), Some(set_scroll_h)), ("scrollV", Some(scroll_v), Some(set_scroll_v)), diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 1f5a6d8cc..7cd74c7d9 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -133,6 +133,10 @@ pub struct EditTextData<'gc> { /// How many lines down the text is offset by. 1-based index. scroll: usize, + /// The limit of characters that can be manually input by the user. + /// Doesn't affect script-triggered modifications. + max_chars: i32, + /// Flags indicating the text field's settings. flags: EditTextFlag, } @@ -278,6 +282,7 @@ impl<'gc> EditText<'gc> { hscroll: 0.0, line_data, scroll: 1, + max_chars: 0, }, )); @@ -1122,6 +1127,14 @@ impl<'gc> EditText<'gc> { self.0.write(context.gc_context).scroll = clamped; } + pub fn max_chars(self) -> i32 { + self.0.read().max_chars + } + + pub fn set_max_chars(self, value: i32, context: &mut UpdateContext<'_, 'gc>) { + self.0.write(context.gc_context).max_chars = value; + } + pub fn screen_position_to_index(self, position: (Twips, Twips)) -> Option { let text = self.0.read(); let position = self.global_to_local(position); @@ -1212,18 +1225,30 @@ impl<'gc> EditText<'gc> { } } code if !(code as char).is_control() => { - self.replace_text( - selection.start(), - selection.end(), - &WString::from_char(character), - context, - ); - let new_start = selection.start() + character.len_utf8(); - self.set_selection( - Some(TextSelection::for_position(new_start)), - context.gc_context, - ); - changed = true; + let can_insert = { + let read = self.0.read(); + let max_chars = read.max_chars; + if max_chars == 0 { + true + } else { + let text_len = read.text_spans.text().len(); + text_len < max_chars.max(0) as usize + } + }; + if can_insert { + self.replace_text( + selection.start(), + selection.end(), + &WString::from_char(character), + context, + ); + let new_start = selection.start() + character.len_utf8(); + self.set_selection( + Some(TextSelection::for_position(new_start)), + context.gc_context, + ); + changed = true; + } } _ => {} }